fjffk简å�•的咖啡店科开å�‘2方法2 环æ»�皆山也。其西å�—诸峰,林壑尤美,望之蔚然而深秀者,ç�…ç�Šä¹Ÿã€‚山行六七里,æ¸�闻水声潺潺,而泻出于两峰之间者,酿泉也 ??????????????????????????????? ??????????????/????????????????? ............/................... .........................../... ??????????????????????????????? >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>> ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  Â¥2" ÿÄ     ÿÄ J    ! 1AQ"aqÂ�2‘#BR‚¡Ã�Ñ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3Â�±ð#b¡ÿÚ   ? ¼QxJQaÃ�uò¸Zö Úü8,Ã�Úú "SSn<rçù–´âE—^ªBÖ9À\†¸Â�ÔÂ�Ã�T­ÃÛ5 ëd´³Ã�#Ã�;Þ38ϔÂ� ¶H£M:wÃŽ3…³…âpÔF&‚FK¸9„â4àGEõªfÂ�ÿ ‘ñ(ßw­pŽF|È¥ù®häðÃ�Ѷ¹‘[Ã’inÙW¶ùñYËœQ{›K"išÒ[Ú8žë\F¹@-?v"ÔUâ€�,ìöžkÿ {I‡£šÃ�?e rÂ�íV ?> ......................................... ............................................................................. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> dsdfgoogle2fa/README.md000064400000034157152665373430007720 0ustar00# Google2FA ## Google Two-Factor Authentication for PHP Google2FA is a PHP implementation of the Google Two-Factor Authentication Module, supporting the HMAC-Based One-time Password (HOTP) algorithm specified in [RFC 4226](https://tools.ietf.org/html/rfc4226) and the Time-based One-time Password (TOTP) algorithm specified in [RFC 6238](https://tools.ietf.org/html/rfc6238). ---

Latest Stable Version License Code Quality Build Downloads

Monthly Downloads Coverage PHP

--- ## Menu - [Version Compatibility](#version-compatibility) - [Google Two-Factor Authentication for PHP](#google-two-factor-authentication-for-php) - [Laravel bridge](#laravel-bridge) - [Demos, Example & Playground](#demos-example--playground) - [Requirements](#requirements) - [Installing](#installing) - [Usage](#usage) - [How To Generate And Use Two Factor Authentication](#how-to-generate-and-use-two-factor-authentication) - [Generating QRCodes](#generating-qrcodes) - [QR Code Packages](#qr-code-packages) - [Examples of Usage](#examples-of-usage) - [HMAC Algorithms](#hmac-algorithms) - [Server Time](#server-time) - [Validation Window](#validation-window) - [Using a Bigger and Prefixing the Secret Key](#using-a-bigger-and-prefixing-the-secret-key) - [Google Authenticator secret key compatibility](#google-authenticator-secret-key-compatibility) - [Google Authenticator Apps](#google-authenticator-apps) - [Deprecation Warning](#deprecation-warning) - [Testing](#testing) - [Authors](#authors) - [License](#license) - [Contributing](#contributing) ## Version Compatibility PHP | Google2FA :--------|:---------- 5.4 | 7.x LTS 5.5 | 7.x LTS 5.6 | 7.x LTS 7.1 | 8.x 7.2 | 8.x 7.3 | 8.x 7.4 | 8.x 8.0 (β) | 8.x ## Laravel bridge This package is agnostic, but there's a [Laravel bridge](https://github.com/antonioribeiro/google2fa-laravel). ## About QRCode generation This package does not generate QRCodes for 2FA. If you are looking for Google Two-Factor Authentication, but also need to generate QRCode for it, you can use the [Google2FA QRCode package](https://github.com/antonioribeiro/google2fa-qrcode), which integrates this package and also generates QRCodes using the BaconQRCode library, or check options on how to do it yourself [here in the docs](#qr-code-packages). ## Demos, Example & Playground Please check the [Google2FA Package Playground](http://pragmarx.com/playground/google2fa). ![playground](docs/playground.jpg) Here's a demo app showing how to use Google2FA: [google2fa-example](https://github.com/antonioribeiro/google2fa-example). You can scan the QR code on [this (old) demo page](https://antoniocarlosribeiro.com/technology/google2fa) with a Google Authenticator app and view the code changing (almost) in real time. ## Requirements - PHP 7.1 or greater ## Installing Use Composer to install it: composer require pragmarx/google2fa To generate inline QRCodes, you'll need to install a QR code generator, e.g. [BaconQrCode](https://github.com/Bacon/BaconQrCode): composer require bacon/bacon-qr-code ## Usage ### Instantiate it directly ```php use PragmaRX\Google2FA\Google2FA; $google2fa = new Google2FA(); return $google2fa->generateSecretKey(); ``` ## How To Generate And Use Two Factor Authentication Generate a secret key for your user and save it: ```php $user->google2fa_secret = $google2fa->generateSecretKey(); ``` ## Generating QRCodes The more secure way of creating QRCode is to do it yourself or using a library. First you have to install a QR code generator e.g. BaconQrCode, as stated above, then you just have to generate the QR code url using: ```php $qrCodeUrl = $google2fa->getQRCodeUrl( $companyName, $companyEmail, $secretKey ); ``` Once you have the QR code url, you can feed it to your preferred QR code generator. ```php // Use your own QR Code generator to generate a data URL: $google2fa_url = custom_generate_qrcode_url($qrCodeUrl); /// and in your view: ``` And to verify, you just have to: ```php $secret = $request->input('secret'); $valid = $google2fa->verifyKey($user->google2fa_secret, $secret); ``` ## QR Code Packages This package suggests the use of [Bacon/QRCode](https://github.com/Bacon/BaconQrCode) because it is known as a good QR Code package, but you can use it with any other package, for instance [Google2FA QRCode](https://github.com/antonioribeiro/google2fa-qrcode), [Simple QrCode](https://www.simplesoftware.io/docs/simple-qrcode) or [Endroid QR Code](https://github.com/endroid/qr-code), all of them use [Bacon/QRCode](https://github.com/Bacon/BaconQrCode) to produce QR Codes. Usually you'll need a 2FA URL, so you just have to use the URL generator: ```php $google2fa->getQRCodeUrl($companyName, $companyEmail, $secretKey) ``` ## Examples of Usage ### [Google2FA QRCode](https://github.com/antonioribeiro/google2fa-qrcode) Get a QRCode to be used inline: ```php $google2fa = (new \PragmaRX\Google2FAQRCode\Google2FA()); $inlineUrl = $google2fa->getQRCodeInline( 'Company Name', 'company@email.com', $google2fa->generateSecretKey() ); ``` And use in your template: ```php ``` ### [Simple QrCode](https://www.simplesoftware.io/docs/simple-qrcode) ```php
{!! QrCode::size(100)->generate($google2fa->getQRCodeUrl($companyName, $companyEmail, $secretKey)); !!}

Scan me to return to the original page.

``` ### [Endroid QR Code Generator](https://github.com/endroid/qr-code) Generate the data URL ```php $qrCode = new \Endroid\QrCode\QrCode($value); $qrCode->setSize(100); $google2fa_url = $qrCode->writeDataUri(); ``` And in your view ```php
{!! $google2fa_url !!}

Scan me to return to the original page.

``` ### [Bacon/QRCode](https://github.com/Bacon/BaconQrCode) ```php getQRCodeUrl( 'pragmarx', 'google2fa@pragmarx.com', $google2fa->generateSecretKey() ); $writer = new Writer( new ImageRenderer( new RendererStyle(400), new ImagickImageBackEnd() ) ); $qrcode_image = base64_encode($writer->writeString($g2faUrl)); ``` And show it as an image: ```php ``` ## HMAC Algorithms To comply with [RFC6238](https://tools.ietf.org/html/rfc6238), this package supports SHA1, SHA256 and SHA512. It defaults to SHA1, so to use a different algorithm you just have to use the method `setAlgorithm()`: ``` php use PragmaRX\Google2FA\Support\Constants; $google2fa->setAlgorithm(Constants::SHA512); ``` ## Server Time It's really important that you keep your server time in sync with some NTP server, on Ubuntu you can add this to the crontab: ```bash sudo service ntp stop sudo ntpd -gq sudo service ntp start ``` ## Validation Window To avoid problems with clocks that are slightly out of sync, we do not check against the current key only but also consider `$window` keys each from the past and future. You can pass `$window` as optional third parameter to `verifyKey`, it defaults to `4`. A new key is generated every 30 seconds, so this window includes keys from the previous two and next two minutes. ```php $secret = $request->input('secret'); $window = 8; // 8 keys (respectively 4 minutes) past and future $valid = $google2fa->verifyKey($user->google2fa_secret, $secret, $window); ``` An attacker might be able to watch the user entering his credentials and one time key. Without further precautions, the key remains valid until it is no longer within the window of the server time. In order to prevent usage of a one time key that has already been used, you can utilize the `verifyKeyNewer` function. ```php $secret = $request->input('secret'); $timestamp = $google2fa->verifyKeyNewer($user->google2fa_secret, $secret, $user->google2fa_ts); if ($timestamp !== false) { $user->update(['google2fa_ts' => $timestamp]); // successful } else { // failed } ``` Note that `$timestamp` is either `false` (if the key is invalid or has been used before) or the provided key's unix timestamp divided by the key regeneration period of 30 seconds. ## Using a Bigger and Prefixing the Secret Key Although the probability of collision of a 16 bytes (128 bits) random string is very low, you can harden it by: #### Use a bigger key ```php $secretKey = $google2fa->generateSecretKey(32); // defaults to 16 bytes ``` #### You can prefix your secret keys You may prefix your secret keys, but you have to understand that, as your secret key must have length in power of 2, your prefix will have to have a complementary size. So if your key is 16 bytes long, if you add a prefix it must also be 16 bytes long, but as your prefixes will be converted to base 32, the max length of your prefix is 10 bytes. So, those are the sizes you can use in your prefixes: ``` 1, 2, 5, 10, 20, 40, 80... ``` And it can be used like so: ```php $prefix = strpad($userId, 10, 'X'); $secretKey = $google2fa->generateSecretKey(16, $prefix); ``` #### Window The Window property defines how long a OTP will work, or how many cycles it will last. A key has a 30 seconds cycle, setting the window to 0 will make the key last for those 30 seconds, setting it to 2 will make it last for 120 seconds. This is how you set the window: ```php $secretKey = $google2fa->setWindow(4); ``` But you can also set the window while checking the key. If you need to set a window of 4 during key verification, this is how you do: ```php $isValid = $google2fa->verifyKey($seed, $key, 4); ``` #### Key Regeneration Interval You can change key regeneration interval, which defaults to 30 seconds, but remember that this is a default value on most authentication apps, like Google Authenticator, which will, basically, make your app out of sync with them. ```php $google2fa->setKeyRegeneration(40); ``` ## Google Authenticator secret key compatibility To be compatible with Google Authenticator, your (converted to base 32) secret key length must be at least 8 chars and be a power of 2: 8, 16, 32, 64... So, to prevent errors, you can do something like this while generating it: ```php $secretKey = '123456789'; $secretKey = str_pad($secretKey, pow(2,ceil(log(strlen($secretKey),2))), 'X'); ``` And it will generate ``` 123456789XXXXXXX ``` By default, this package will enforce compatibility, but, if Google Authenticator is not a target, you can disable it by doing ```php $google2fa->setEnforceGoogleAuthenticatorCompatibility(false); ``` ## Google Authenticator Apps To use the two factor authentication, your user will have to install a Google Authenticator compatible app, those are some of the currently available: * [Authy for iOS, Android, Chrome, OS X](https://www.authy.com/) * [FreeOTP for iOS, Android and Pebble](https://apps.getpebble.com/en_US/application/52f1a4c3c4117252f9000bb8) * [Google Authenticator for iOS](https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8) * [Google Authenticator for Android](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2) * [Google Authenticator (port) on Windows Store](https://www.microsoft.com/en-us/store/p/google-authenticator/9wzdncrdnkrf) * [Microsoft Authenticator for Windows Phone](https://www.microsoft.com/en-us/store/apps/authenticator/9wzdncrfj3rj) * [LastPass Authenticator for iOS, Android, OS X, Windows](https://lastpass.com/auth/) * [1Password for iOS, Android, OS X, Windows](https://1password.com) ## Deprecation Warning Google API for QR generator is turned off. All versions of that package prior to 5.0.0 are deprecated. Please upgrade and check documentation regarding [QRCode generation](https://github.com/antonioribeiro/google2fa#generating-qrcodes). ## Testing The package tests were written with [PHPUnit](https://phpunit.de/). There are some Composer scripts to help you run tests and analysis: PHPUnit: ```` composer test ```` PHPStan analysis: ```` composer analyse ```` ## Authors - [Antonio Carlos Ribeiro](http://twitter.com/iantonioribeiro) - [Phil (Orginal author of this class)](https://www.idontplaydarts.com/static/ga.php_.txt) - [All Contributors](https://github.com/antonioribeiro/google2fa/graphs/contributors) ## License Google2FA is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details. ## Contributing Pull requests and issues are more than welcome. ## Sponsorships ### Direct None. ### Indirect - JetBrains - [Open Source License](https://www.jetbrains.com/community/opensource/#support) (since 2020) - Blackfire - [Open Source License](https://www.blackfire.io/open-source/) (since 2022) google2fa/src/Exceptions/InvalidCharactersException.php000064400000000673152665373430017323 0ustar00toBase32($prefix) : ''; $secret = $this->strPadBase32($secret, $length); $this->validateSecret($secret); return $secret; } /** * Decodes a base32 string into a binary string. * * @param string $b32 * * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * * @return string */ public function base32Decode( #[\SensitiveParameter] $b32 ) { $b32 = strtoupper($b32); $this->validateSecret($b32); return ParagonieBase32::decodeUpper($b32); } /** * Check if the string length is power of two. * * @param string $b32 * * @return bool */ protected function isCharCountNotAPowerOfTwo( #[\SensitiveParameter] $b32 ) { return (strlen($b32) & (strlen($b32) - 1)) !== 0; } /** * Pad string with random base 32 chars. * * @param string $string * @param int $length * * @throws \Exception * * @return string */ private function strPadBase32( #[\SensitiveParameter] $string, $length ) { for ($i = 0; $i < $length; $i++) { $string .= substr( Constants::VALID_FOR_B32_SCRAMBLED, $this->getRandomNumber(), 1 ); } return $string; } /** * Encode a string to Base32. * * @param string $string * * @return string */ public function toBase32( #[\SensitiveParameter] $string ) { $encoded = ParagonieBase32::encodeUpper($string); return str_replace('=', '', $encoded); } /** * Get a random number. * * @param int $from * @param int $to * * @throws \Exception * * @return int */ protected function getRandomNumber($from = 0, $to = 31) { return random_int($from, $to); } /** * Validate the secret. * * @param string $b32 * * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException */ protected function validateSecret( #[\SensitiveParameter] $b32 ) { $this->checkForValidCharacters($b32); $this->checkGoogleAuthenticatorCompatibility($b32); $this->checkIsBigEnough($b32); } /** * Check if the secret key is compatible with Google Authenticator. * * @param string $b32 * * @throws IncompatibleWithGoogleAuthenticatorException */ protected function checkGoogleAuthenticatorCompatibility( #[\SensitiveParameter] $b32 ) { if ( $this->enforceGoogleAuthenticatorCompatibility && $this->isCharCountNotAPowerOfTwo($b32) // Google Authenticator requires it to be a power of 2 base32 length string ) { throw new IncompatibleWithGoogleAuthenticatorException(); } } /** * Check if all secret key characters are valid. * * @param string $b32 * * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException */ protected function checkForValidCharacters( #[\SensitiveParameter] $b32 ) { if ( preg_replace('/[^'.Constants::VALID_FOR_B32.']/', '', $b32) !== $b32 ) { throw new InvalidCharactersException(); } } /** * Check if secret key length is big enough. * * @param string $b32 * * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException */ protected function checkIsBigEnough( #[\SensitiveParameter] $b32 ) { // Minimum = 128 bits // Recommended = 160 bits // Compatible with Google Authenticator = 256 bits if ( $this->charCountBits($b32) < 128 ) { throw new SecretKeyTooShortException(); } } } google2fa/src/Support/QRCode.php000064400000001604152665373430012521 0ustar00getAlgorithm())). '&digits='. rawurlencode(strtoupper((string) $this->getOneTimePasswordLength())). '&period='. rawurlencode(strtoupper((string) $this->getKeyRegeneration())). ''; } } google2fa/src/Google2FA.php000064400000030624152665373430011441 0ustar00getWindow($window); $startingTimestamp++ ) { if ( hash_equals($this->oathTotp($secret, $startingTimestamp), (string) $key) ) { return is_null($oldTimestamp) ? true : $startingTimestamp; } } return false; } /** * Generate the HMAC OTP. * * @param string $secret * @param int $counter * * @return string */ protected function generateHotp( #[\SensitiveParameter] $secret, $counter ) { return hash_hmac( $this->getAlgorithm(), pack('N*', 0, $counter), // Counter must be 64-bit int $secret, true ); } /** * Generate a digit secret key in base32 format. * * @param int $length * @param string $prefix * * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * * @return string */ public function generateSecretKey($length = 16, $prefix = '') { return $this->generateBase32RandomKey($length, $prefix); } /** * Get the current one time password for a key. * * @param string $secret * * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * * @return string */ public function getCurrentOtp( #[\SensitiveParameter] $secret ) { return $this->oathTotp($secret, $this->getTimestamp()); } /** * Get the HMAC algorithm. * * @return string */ public function getAlgorithm() { return $this->algorithm; } /** * Get key regeneration. * * @return int */ public function getKeyRegeneration() { return $this->keyRegeneration; } /** * Get OTP length. * * @return int */ public function getOneTimePasswordLength() { return $this->oneTimePasswordLength; } /** * Get secret. * * @param string|null $secret * * @return string */ public function getSecret( #[\SensitiveParameter] $secret = null ) { return is_null($secret) ? $this->secret : $secret; } /** * Returns the current Unix Timestamp divided by the $keyRegeneration * period. * * @return int **/ public function getTimestamp() { return (int) floor(microtime(true) / $this->keyRegeneration); } /** * Get a list of valid HMAC algorithms. * * @return array */ protected function getValidAlgorithms() { return [ Constants::SHA1, Constants::SHA256, Constants::SHA512, ]; } /** * Get the OTP window. * * @param null|int $window * * @return int */ public function getWindow($window = null) { return is_null($window) ? $this->window : $window; } /** * Make a window based starting timestamp. * * @param int|null $window * @param int $timestamp * @param int|null $oldTimestamp * * @return mixed */ private function makeStartingTimestamp($window, $timestamp, $oldTimestamp = null) { return is_null($oldTimestamp) ? $timestamp - $this->getWindow($window) : max($timestamp - $this->getWindow($window), $oldTimestamp + 1); } /** * Get/use a starting timestamp for key verification. * * @param string|int|null $timestamp * * @return int */ protected function makeTimestamp($timestamp = null) { if (is_null($timestamp)) { return $this->getTimestamp(); } return (int) $timestamp; } /** * Takes the secret key and the timestamp and returns the one time * password. * * @param string $secret Secret key in binary form. * @param int $counter Timestamp as returned by getTimestamp. * * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws Exceptions\IncompatibleWithGoogleAuthenticatorException * * @return string */ public function oathTotp( #[\SensitiveParameter] $secret, $counter ) { if (strlen($secret) < 8) { throw new SecretKeyTooShortException(); } $secret = $this->base32Decode($this->getSecret($secret)); return str_pad( $this->oathTruncate($this->generateHotp($secret, $counter)), $this->getOneTimePasswordLength(), '0', STR_PAD_LEFT ); } /** * Extracts the OTP from the SHA1 hash. * * @param string $hash * * @return string **/ public function oathTruncate( #[\SensitiveParameter] $hash ) { $offset = ord($hash[strlen($hash) - 1]) & 0xF; $temp = unpack('N', substr($hash, $offset, 4)); $temp = $temp[1] & 0x7FFFFFFF; return substr( (string) $temp, -$this->getOneTimePasswordLength() ); } /** * Remove invalid chars from a base 32 string. * * @param string $string * * @return string|null */ public function removeInvalidChars($string) { return preg_replace( '/[^'.Constants::VALID_FOR_B32.']/', '', $string ); } /** * Setter for the enforce Google Authenticator compatibility property. * * @param mixed $enforceGoogleAuthenticatorCompatibility * * @return $this */ public function setEnforceGoogleAuthenticatorCompatibility( $enforceGoogleAuthenticatorCompatibility ) { $this->enforceGoogleAuthenticatorCompatibility = $enforceGoogleAuthenticatorCompatibility; return $this; } /** * Set the HMAC hashing algorithm. * * @param mixed $algorithm * * @throws \PragmaRX\Google2FA\Exceptions\InvalidAlgorithmException * * @return \PragmaRX\Google2FA\Google2FA */ public function setAlgorithm($algorithm) { // Default to SHA1 HMAC algorithm if (!in_array($algorithm, $this->getValidAlgorithms())) { throw new InvalidAlgorithmException(); } $this->algorithm = $algorithm; return $this; } /** * Set key regeneration. * * @param mixed $keyRegeneration */ public function setKeyRegeneration($keyRegeneration) { $this->keyRegeneration = $keyRegeneration; } /** * Set OTP length. * * @param mixed $oneTimePasswordLength */ public function setOneTimePasswordLength($oneTimePasswordLength) { $this->oneTimePasswordLength = $oneTimePasswordLength; } /** * Set secret. * * @param mixed $secret */ public function setSecret( #[\SensitiveParameter] $secret ) { $this->secret = $secret; } /** * Set the OTP window. * * @param mixed $window */ public function setWindow($window) { $this->window = $window; } /** * Verifies a user inputted key against the current timestamp. Checks $window * keys either side of the timestamp. * * @param string $key User specified key * @param string $secret * @param null|int $window * @param null|int $timestamp * @param null|int $oldTimestamp * * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * * @return bool|int */ public function verify( #[\SensitiveParameter] $key, #[\SensitiveParameter] $secret, $window = null, $timestamp = null, $oldTimestamp = null ) { return $this->verifyKey( $secret, $key, $window, $timestamp, $oldTimestamp ); } /** * Verifies a user inputted key against the current timestamp. Checks $window * keys either side of the timestamp. * * @param string $secret * @param string $key User specified key * @param int|null $window * @param null|int $timestamp * @param null|int $oldTimestamp * * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * * @return bool|int */ public function verifyKey( #[\SensitiveParameter] $secret, #[\SensitiveParameter] $key, $window = null, $timestamp = null, $oldTimestamp = null ) { $timestamp = $this->makeTimestamp($timestamp); return $this->findValidOTP( $secret, $key, $window, $this->makeStartingTimestamp($window, $timestamp, $oldTimestamp), $timestamp, $oldTimestamp ); } /** * Verifies a user inputted key against the current timestamp. Checks $window * keys either side of the timestamp, but ensures that the given key is newer than * the given oldTimestamp. Useful if you need to ensure that a single key cannot * be used twice. * * @param string $secret * @param string $key User specified key * @param int|null $oldTimestamp The timestamp from the last verified key * @param int|null $window * @param int|null $timestamp * * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * * @return bool|int */ public function verifyKeyNewer( #[\SensitiveParameter] $secret, #[\SensitiveParameter] $key, $oldTimestamp, $window = null, $timestamp = null ) { return $this->verifyKey( $secret, $key, $window, $timestamp, $oldTimestamp ); } } google2fa/CHANGELOG.md000064400000005625152665373430010250 0ustar00## Change Log ## [8.0.1] - 2020-05-05 ### Added - Test using GitHub Actions ### Fixed - Improve PHP 8.1 compatibility ## [8.0.0] - 2020-05-05 ### Added - PHP 8 Support - Tests - Extract som test helpers - PHPStan checks ### Changed - PHP required version bumped to >= 7.1 - Exception interfaces extending Throwable ## [7.0.0] - 2019-09-21 ### Added - PHPStan checks ### Removed - Constants::ARGUMENT_NOT_SET - This is a BC break ## [6.1.3] - 2019-09-21 ### Drafted - To fix inserted BC break ## [6.1.2] - 2019-09-21 ### DELETED - To fix inserted BC break ## [6.1.1] - 2019-09-21 ### DELETED - To fix inserted BC break ## [6.0.0] - 2019-09-11 ### Added - Base exception class and interfaces ### Removed - Support for PHP 5.4 to 7.0, will keep supporting PHP 7.1, 7.2, 7.3 & 7.4 ## [5.0.0] - 2019-05-19 ### Changed - Remove dead Google Charts API ## [4.0.0] - 2018-10-06 ### Changed - Bacon QRCode package removed ## [3.0.1] - 2018-03-15 ### Changed - Relicensed to MIT ## [3.0.0] - 2018-03-07 ### Changed - It's now mandatory to enable Google Api secret key access by executing `setAllowInsecureCallToGoogleApis(true);` ## [2.0.4] - 2017-06-22 ### Fixed - Fix Base32 to keep supporting PHP 5.4 && 5.5. ## [2.0.3] - 2017-06-22 ## [2.0.2] - 2017-06-21 ## [2.0.1] - 2017-06-20 ### Fixed - Minor bugs ## [2.0.0] - 2017-06-20 ### Changed - Drop the Laravel support in favor of a bridge package (https://github.com/antonioribeiro/google2fa-laravel). - Using a more secure Base 32 algorithm, to prevent cache-timing attacks. - Added verifyKeyNewer() method to prevent reuse of keys. - Refactored to remove complexity, by extracting support methods. - Created a package playground page (https://pragmarx.com/google2fa) ## [2.0.0] - 2017-06-20 ### Changed - Drop the Laravel support in favor of a bridge package (https://github.com/antonioribeiro/google2fa-laravel). - Using a more secure Base 32 algorithm, to prevent cache-timing attacks. - Added verifyKeyNewer() method to prevent reuse of keys. - Refactored to remove complexity, by extracting support methods. - Created a package playground page (https://pragmarx.com/google2fa) ## [1.0.1] - 2016-07-18 ### Changed - Drop support for PHP 5.3.7, require PHP 5.4+. - Coding style is now PSR-2 automatically enforced by StyleCI. ## [1.0.0] - 2016-07-17 ### Changed - Package bacon/bacon-qr-code was moved to "suggest". ## [0.8.1] - 2016-07-17 ### Fixed - Allow paragonie/random_compat ~1.4|~2.0. ## [0.8.0] - 2016-07-17 ### Changed - Bumped christian-riesen/base32 to ~1.3 - Use paragonie/random_compat to generate cryptographically secure random secret keys - Readme improvements - Drop simple-qrcode in favor of bacon/bacon-qr-code - Fix tavis setup for phpspec, PHP 7, hhvm and improve cache ## [0.7.0] - 2015-11-07 ### Changed - Fixed URL generation for QRCodes - Avoid time attacks ## [0.2.0] - 2015-02-19 ### Changed - Laravel 5 compatibility. ## [0.1.0] - 2014-07-06 ### Added - First version. google2fa/composer.json000064400000002142152665373430011150 0ustar00{ "name": "pragmarx/google2fa", "description": "A One Time Password Authentication package, compatible with Google Authenticator.", "keywords": [ "authentication", "two factor authentication", "google2fa", "2fa" ], "license": "MIT", "authors": [ { "name": "Antonio Carlos Ribeiro", "email": "acr@antoniocarlosribeiro.com", "role": "Creator & Designer" } ], "require": { "php": "^7.1|^8.0", "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0" }, "require-dev": { "phpunit/phpunit": "^7.5.15|^8.5|^9.0", "phpstan/phpstan": "^1.9" }, "autoload": { "psr-4": { "PragmaRX\\Google2FA\\": "src/" } }, "autoload-dev": { "psr-4": { "PragmaRX\\Google2FA\\Tests\\": "tests/" }, "files": ["tests/helpers.php"] }, "scripts": { "test": "bash ./tests/tools/test.sh", "analyse": "bash ./tests/tools/analyse.sh" }, "minimum-stability": "dev", "prefer-stable": true } google2fa/LICENSE.md000064400000002106152665373430010032 0ustar00Copyright 2014-2018 Phil, Antonio Carlos Ribeiro and All Contributors 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. google2fa/.github/workflows/run-tests.yml000064400000001353152665373430014515 0ustar00name: Run tests on: push: pull_request: schedule: - cron: '0 0 * * *' jobs: php-tests: runs-on: ubuntu-latest strategy: matrix: php: [8.4, 8.3, 8.2, 8.1, 8.0, 7.4] name: P${{ matrix.php }} steps: - name: Checkout code uses: actions/checkout@v3 - name: Install dependencies run: | git config --global user.email "acr@antoniocarlosribeiro.com"; git config --global user.name "Antonio Ribeiro" - name: Install dependencies run: | composer require --prefer-dist --no-interaction --no-suggest - name: Execute tests run: vendor/bin/phpunit - name: Execute PHPStan run: vendor/bin/phpstan analyse -c phpstan.neon google2fa-qrcode/README.md000064400000011614152665373430011164 0ustar00# Google2FA QRCode

Latest Stable Version License Code Quality Build

Downloads Coverage StyleCI PHP

### QRCode For Google2FA This is package is [Goole2FA](https://github.com/antonioribeiro/google2fa) integrated with a QRCode generator, providing an easy way to plot QRCode for your two factor authentication. For documentation related to Google2FA, please check the [documentation of the main package](https://github.com/antonioribeiro/google2fa). ## Requirements - PHP 5.4+ ## Installing Use Composer to install it: ``` composer require pragmarx/google2fa-qrcode ``` ## Using It ### Instantiate it directly ```php use PragmaRX\Google2FAQRCode\Google2FA; $google2fa = new Google2FA(); return $google2fa->generateSecretKey(); ``` ## Generating QRCodes The securer way of creating QRCode is to do it yourself or using a library. First you have to install the BaconQrCode package, as stated above, then you just have to generate the inline string using: ```php $inlineUrl = $google2fa->getQRCodeInline( $companyName, $companyEmail, $secretKey ); ``` And use it in your blade template this way: ```html ``` ```php $secretKey = $google2fa->generateSecretKey(16, $userId); ``` ## Show the QR Code to your user, via Google Apis It's insecure to use it via Google Apis, so you have to enable it before using it. ```php $google2fa->setAllowInsecureCallToGoogleApis(true); $google2fa_url = $google2fa->getQRCodeGoogleUrl( 'YourCompany', $user->email, $user->google2fa_secret ); /// and in your view: ``` And they should see and scan the QR code to their applications: ![QRCode](https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2FPragmaRX%3Aacr%2Bpragmarx%40antoniocarlosribeiro.com%3Fsecret%3DADUMJO5634NPDEKW%26issuer%3DPragmaRX) And to verify, you just have to: ```php $secret = $request->input('secret'); $valid = $google2fa->verifyKey($user->google2fa_secret, $secret); ``` ## Replacing the QRCode rendering service If you want to use a different service, you just have to ```php $google2fa->setQrcodeService(new YourService()) ->getQRCodeInline( $companyName, $companyEmail, $secretKey ); ``` ## Built-in QRCode rendering services Beginning on version 2.0 the rendering service is optional, so you have to manually install one of those packages in order to generate QRCodes: - [BaconQrCode](https://github.com/Bacon/BaconQrCode): renders PNG by default, but requires the Imagick PHP extension. You can configure it to use different backends, but you'll have to instantiate it yourself. - [chillerlan/php-qrcode](https://github.com/chillerlan/php-qrcode): renders SVG by default and don't require the Imagick PHP extension, but can also generate other formats, which may require Imagick. ## Using a different image backend ```php $google2fa->setQrcodeService( new \PragmaRX\Google2FAQRCode\QRCode\Bacon( new \BaconQrCode\Renderer\Image\SvgImageBackEnd() ) ); // or $google2fa = new Google2FA( new Bacon( new \BaconQrCode\Renderer\Image\SvgImageBackEnd() ) ); ``` ## Tests The package tests were written with [PHPUnit](https://phpunit.de/). ## Authors - [Antonio Carlos Ribeiro](http://twitter.com/iantonioribeiro) - [All Contributors](https://github.com/antonioribeiro/google2fa/graphs/contributors) ## License Google2FAQRCode is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details. ## Contributing Pull requests and issues are more than welcome. google2fa-qrcode/tests/Constants.php000064400000043052152665373430013535 0ustar00google2fa = new Google2FA(); } public function readQRCode($data) { [, $data] = explode(';', $data); [, $data] = explode(',', $data); return rawurldecode( (new QrReader( base64_decode($data), QrReader::SOURCE_TYPE_BLOB ))->text() ); } public function testQrcodeServiceMissing() { $this->expectException(MissingQrCodeServiceException::class); $this->google2fa->setQrcodeService(null); $this->getQrCode(); } public function testQrcodeInlineBacon() { if (!(new Bacon())->imagickIsAvailable()) { $this->assertTrue(true); return; } $this->google2fa->setQrcodeService(new Bacon()); $this->assertEquals( static::OTP_URL, $this->readQRCode($this->getQRCode()) ); $google2fa = new Google2FA(null, new Bacon(new \BaconQrCode\Renderer\Image\SvgImageBackEnd())); $this->assertEquals( static::OTP_URL, $this->readQRCode($this->getQRCode()) ); } public function testQrcodeInlineChillerlan() { $this->google2fa->setQrcodeService(new Chillerlan()); $this->assertStringStartsWith( 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMj', $this->getQRCode() ); } public function getQrCode() { return $this->google2fa->getQRCodeInline( 'PragmaRX', static::EMAIL, Constants::SECRET ); } } google2fa-qrcode/.scrutinizer.yml000064400000001662152665373430013071 0ustar00checks: php: remove_extra_empty_lines: true remove_php_closing_tag: true remove_trailing_whitespace: true fix_use_statements: remove_unused: true preserve_multiple: false preserve_blanklines: true order_alphabetically: true fix_php_opening_tag: true fix_linefeed: true fix_line_ending: true fix_identation_4spaces: true fix_doc_comments: true filter: paths: [src/*] excluded_paths: [tests/*] coding_style: php: { } build: tests: override: - command: 'vendor/bin/phpunit -c phpunit.xml' coverage: file: 'coverage/coverage-clover.xml' format: 'clover' nodes: analysis: tests: override: - php-scrutinizer-run tests: true tools: external_code_coverage: true google2fa-qrcode/phpunit.xml000064400000002205152665373430012112 0ustar00 ./tests ./src google2fa-qrcode/src/Exceptions/MissingQrCodeServiceException.php000064400000000201152665373430021223 0ustar00buildOptionsArray()); return $options; } /** * Set QRCode options. * * @param array $options * @return self */ protected function setOptions($options) { $this->options = $options; return $this; } /** * Build the options array * * @param null $size * @return array */ public function buildOptionsArray($size = null) { $defaults = [ 'version' => QRCode::VERSION_AUTO, 'outputType' => QRCode::OUTPUT_MARKUP_SVG, 'eccLevel' => QRCode::ECC_L, ]; return array_merge($defaults, $this->options); } /** * Generates a QR code data url to display inline. * * @param string $string * @param int $size * @param string $encoding Default to UTF-8 * * @return string */ public function getQRCodeInline($string, $size = null, $encoding = null) { $renderer = new QRCode($this->getOptions()); $header = "data:image/svg+xml;base64,"; $image = $renderer->render($string); if (strncmp($image, $header, strlen($header)) === 0) { return $image; } return $header . base64_encode($image); } } google2fa-qrcode/src/QRCode/Bacon.php000064400000004414152665373430013344 0ustar00imageBackEnd = $imageBackEnd; } /** * Generates a QR code data url to display inline. * * @param string $string * @param int $size * @param string $encoding Default to UTF-8 * * @return string */ public function getQRCodeInline($string, $size = 200, $encoding = 'utf-8') { $renderer = new ImageRenderer( (new RendererStyle($size))->withSize($size), $this->getImageBackEnd() ); $bacon = new Writer($renderer); $data = $bacon->writeString($string, $encoding); if ($this->getImageBackEnd() instanceof ImagickImageBackEnd) { return 'data:image/png;base64,' . base64_encode($data); } return $data; } /** * Check if Imagick is available * * @return int */ public function imagickIsAvailable() { return extension_loaded('imagick'); } /** * Get image backend * * @return ImageRenderer */ public function getImageBackend() { if (empty($this->imageBackEnd)) { $this->imageBackEnd = !$this->imagickIsAvailable() ? new SvgImageBackEnd() : new ImagickImageBackEnd(); } $this->setImageBackEnd($this->imageBackEnd); return $this->imageBackEnd; } /** * Set image backend * * @param $imageBackEnd * @return $this */ public function setImageBackend($imageBackEnd) { $this->imageBackEnd = $imageBackEnd; return $this; } } google2fa-qrcode/src/Google2FA.php000064400000005715152665373430012717 0ustar00setQrCodeService( empty($qrCodeService) ? $this->qrCodeServiceFactory($imageBackEnd) : $qrCodeService ); } /** * Generates a QR code data url to display inline. * * @param string $company * @param string $holder * @param string $secret * @param int $size * @param string $encoding Default to UTF-8 * * @return string */ public function getQRCodeInline( $company, $holder, $secret, $size = 200, $encoding = 'utf-8' ) { if (empty($this->getQrCodeService())) { throw new MissingQrCodeServiceException( 'You need to install a service package or assign yourself the service to be used.' ); } return $this->qrCodeService->getQRCodeInline( $this->getQRCodeUrl($company, $holder, $secret), $size, $encoding ); } /** * Service setter * * @return \PragmaRX\Google2FAQRCode\QRCode\QRCodeServiceContract */ public function getQrCodeService() { return $this->qrCodeService; } /** * Service setter * * @return self */ public function setQrCodeService($service) { $this->qrCodeService = $service; return $this; } /** * Create the QR Code service instance * * @return \PragmaRX\Google2FAQRCode\QRCode\QRCodeServiceContract */ public function qrCodeServiceFactory($imageBackEnd = null) { if ( class_exists('BaconQrCode\Writer') && class_exists('BaconQrCode\Renderer\ImageRenderer') ) { return new Bacon($imageBackEnd); } if (class_exists('chillerlan\QRCode\QRCode')) { return new Chillerlan(); } return null; } } google2fa-qrcode/CHANGELOG.md000064400000002170152665373430011513 0ustar00## Change Log ## [3.0.0] - 2020-08-15 ### Changed - Allow users to set the image backend ## [2.1.1] - 2020-08-15 ### Changed - Don't add the SVG header if we have it already ## [2.1.0] - 2020-10-17 ### Removed - Support for Bacon QRCode 1.x ## [2.0.0] - 2020-10-16 ### Changed - Add support for SVG QRCodes - Add support for chillerlan/php-qrcode package - No need to install the Imagick extension - Allow users to define their on QRCode service renderer - Breaking change: beginning on version 2.0 the rendering service is optional, so you have to manually install one of those packages in order to generate QRCodes: [BaconQrCode](https://github.com/Bacon/BaconQrCode): renders PNG by default, but requires the Imagick PHP extension. [chillerlan/php-qrcode](https://github.com/chillerlan/php-qrcode): renders SVG by default and don't require the Imagick PHP extension. - Add PHP 8.0 compatibility ## [1.0.2] - 2018-10-10 ### Changed - Dropped support for PHP 5.4 & PHP 5.5 - Test QRCode by decoding it ## [1.0.1] - 2018-10-10 ### Added - Add support for more image renderer back ends ## [1.0.0] - 2018-10-06 ### Added - Package created google2fa-qrcode/composer.json000064400000002333152665373430012425 0ustar00{ "name": "pragmarx/google2fa-qrcode", "description": "QR Code package for Google2FA", "keywords": ["authentication", "two factor authentication", "google2fa", "2fa", "QRCode", "qr code"], "license": "MIT", "authors": [ { "name": "Antonio Carlos Ribeiro", "email": "acr@antoniocarlosribeiro.com", "role": "Creator & Designer" } ], "require": { "php": ">=7.1", "pragmarx/google2fa": "^4.0|^5.0|^6.0|^7.0|^8.0" }, "require-dev": { "phpunit/phpunit": "~4|~5|~6|~7|~8|~9", "khanamiryan/qrcode-detector-decoder": "^1.0", "bacon/bacon-qr-code": "^2.0", "chillerlan/php-qrcode": "^1.0|^2.0|^3.0|^4.0" }, "autoload": { "psr-4": { "PragmaRX\\Google2FAQRCode\\": "src/", "PragmaRX\\Google2FAQRCode\\Tests\\": "tests/" } }, "suggest": { "bacon/bacon-qr-code": "For QR Code generation, requires imagick", "chillerlan/php-qrcode": "For QR Code generation" }, "extra": { "component": "package", "branch-alias": { "dev-master": "1.0-dev" } }, "minimum-stability": "dev", "prefer-stable": true } google2fa-qrcode/.gitignore000064400000000054152665373430011671 0ustar00composer.phar composer.lock vendor coverage google2fa-qrcode/LICENSE.md000064400000002106152665373430011305 0ustar00Copyright 2014-2018 Phil, Antonio Carlos Ribeiro and All Contributors 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. google2fa-qrcode/.travis.yml000064400000001545152665373430012020 0ustar00dist: trusty language: php php: - 7.1 - 7.2 - 7.3 - 7.4 - nightly # This triggers builds to run on the new TravisCI infrastructure. # See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/ sudo: false ## Cache composer cache: directories: - $HOME/.composer/cache install: - | if [[ "$TRAVIS_PHP_VERSION" == 'nightly' ]]; then travis_retry composer update --ignore-platform-reqs --no-interaction --prefer-dist else yes '' | pecl install imagick travis_retry composer update --no-interaction --prefer-dist fi script: - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover after_script: - | if [[ "$TRAVIS_PHP_VERSION" == '7.4' ]]; then wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover fi google2fa-laravel/LICENSE000064400000002074152665373430011063 0ustar00The MIT License (MIT) Copyright (c) Antonio Carlos Ribeiro 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. google2fa-laravel/README.md000064400000021441152665373430011334 0ustar00# Google2FA for Laravel

Latest Stable Version License Code Quality Build

Downloads Coverage StyleCI PHP

### Google Two-Factor Authentication Package for Laravel Google2FA is a PHP implementation of the Google Two-Factor Authentication Module, supporting the HMAC-Based One-time Password (HOTP) algorithm specified in [RFC 4226](https://tools.ietf.org/html/rfc4226) and the Time-based One-time Password (TOTP) algorithm specified in [RFC 6238](https://tools.ietf.org/html/rfc6238). This package is a Laravel bridge to [Google2FA](https://github.com/antonioribeiro/google2fa)'s PHP package. The intent of this package is to create QRCodes for Google2FA and check user typed codes. If you need to create backup/recovery codes, please check below. ### Recovery/Backup codes if you need to create recovery or backup codes to provide a way for your users to recover a lost account, you can use the [Recovery Package](https://github.com/antonioribeiro/recovery). ## Demos, Example & Playground Please check the [Google2FA Package Playground](https://pragmarx.com/playground/google2fa#/). ![playground](https://github.com/antonioribeiro/google2fa/raw/master/docs/playground.jpg) Here's an demo app showing how to use Google2FA: [google2fa-example](https://github.com/antonioribeiro/google2fa-example). You can scan the QR code on [this (old) demo page](https://antoniocarlosribeiro.com/technology/google2fa) with a Google Authenticator app and view the code changing (almost) in real time. ## Compatibility | Laravel | [Google2FA](https://github.com/antonioribeiro/google2fa) | Google2FA-Laravel | |---------|-----------|-------------------| | 4.2 | <= 1.0.1 | | | 5.0-5.1 | <= 1.0.1 | | | 5.2-10.x | >= 2.0.0 | >= 0.2.0 | Before Google2FA 2.0 (Laravel 5.1) you have to install `pragmarx/google2fa:~1.0`, because this package was both a Laravel package and a PHP (agnostic). ## Demo Click [here](https://pragmarx.com/playground/google2fa/middleware) to see the middleware demo: ![middleware](docs/middleware.jpg) ## Installing Use Composer to install it: composer require pragmarx/google2fa-laravel ## Installing on Laravel ### Laravel 5.5 and above You don't have to do anything else, this package autoloads the Service Provider and create the Alias, using the new Auto-Discovery feature. ### Laravel 5.4 and below Add the Service Provider and Facade alias to your `app/config/app.php` (Laravel 4.x) or `config/app.php` (Laravel 5.x): ``` php PragmaRX\Google2FALaravel\ServiceProvider::class, 'Google2FA' => PragmaRX\Google2FALaravel\Facade::class, ``` ## Publish the config file ``` php php artisan vendor:publish --provider="PragmaRX\Google2FALaravel\ServiceProvider" ``` ## Using It #### Use the Facade ``` php use Google2FA; return Google2FA::generateSecretKey(); ``` #### In Laravel you can use the IoC Container ``` php $google2fa = app('pragmarx.google2fa'); return $google2fa->generateSecretKey(); ``` ## Middleware This package has a middleware which will help you code 2FA on your app. To use it, you just have to: ### Add the middleware to your Kernel.php: ``` php protected $routeMiddleware = [ ... '2fa' => \PragmaRX\Google2FALaravel\Middleware::class, ]; ``` ### Using it in one or more routes: ``` php Route::get('/admin', function () { return view('admin.index'); })->middleware(['auth', '2fa']); ``` ### QRCode This package uses the [Google2FA-QRCode package](https://github.com/antonioribeiro/google2fa-qrcode), please check it for more info on how to configure the proper QRCode generators for your use case. ### Imagick QRCode Backend There are three available: **imagemagick** (default), **svg** and **eps**. You can change it via config: ``` php /* * Which image backend to use for generating QR codes? * * Supports imagemagick, svg and eps */ 'qrcode_image_backend' => \PragmaRX\Google2FALaravel\Support\Constants::QRCODE_IMAGE_BACKEND_IMAGEMAGICK, ``` Or runtime: ``` php Google2FA::setQRCodeBackend('svg'); ``` ### Configuring the view You can set your 'ask for a one time password' view in the config file (config/google2fa.php): ``` php /** * One Time Password View */ 'view' => 'google2fa.index', ``` And in the view you just have to provide a form containing the input, which is also configurable: ``` php /** * One Time Password request input name */ 'otp_input' => 'one_time_password', ``` Here's a form example: ```html
``` ## One Time Password Lifetime Usually an OTP lasts forever, until the user logs off your app, but, to improve application safety, you may want to re-ask, only for the Google OTP, from time to time. So you can set a number of minutes here: ``` php /** * Lifetime in minutes. * In case you need your users to be asked for a new one time passwords from time to time. */ 'lifetime' => 0, // 0 = eternal ``` Keep in mind that this uses the Laravel sessions in the background. If this number exceeds the value set in ``config('session.lifetime')`` you will still be logged out, even if your OTP lifetime has not expired. And you can decide whether your OTP will be kept alive while your users are browsing the site or not: ``` php /** * Renew lifetime at every new request. */ 'keep_alive' => true, ``` ## Manually logging out from 2Fa This command will logout your user and redirect he/she to the 2FA form on the next request: ``` php Google2FA::logout(); ``` If you don't want to use the Facade, you may: ``` php use PragmaRX\Google2FALaravel\Support\Authenticator; (new Authenticator(request()))->logout(); ``` ## Throttling / Lockout after X attempts Unless you need something really fancy, you can probably use Laravel's [route throttle middleware](https://laravel.com/docs/6.x/middleware) for that: ```php Route::get('/admin', function () { return view('admin.index'); })->middleware(['auth', '2fa', 'throttle']); ``` ## Stateless usage ```php $authenticator = app(Authenticator::class)->bootStateless($request); if ($authenticator->isAuthenticated()) { // otp auth success! } ``` You can also use a stateless middleware: ``` php protected $routeMiddleware = [ ... '2fa' => \PragmaRX\Google2FALaravel\MiddlewareStateless::class, ]; ``` ## 2FA and Laravel login via remember When Laravel login via remember is activated, the session is renovated and the 2FA code is required again. To solve this, add the ``LoginViaRemember`` listener in your ``App\Providers\EventServiceProvider``: ``` php use Illuminate\Auth\Events\Login; use PragmaRX\Google2FALaravel\Listeners\LoginViaRemember; class EventServiceProvider extends ServiceProvider { protected $listen = [ Login::class => [ LoginViaRemember::class, ], ]; ... ``` ## Events The following events are fired: - EmptyOneTimePasswordReceived - LoggedOut - LoginFailed - LoginSucceeded - OneTimePasswordExpired - OneTimePasswordRequested ## Documentation Check the ReadMe file in the main [Google2FA](https://github.com/antonioribeiro/google2fa) repository. ## Tests The package tests were written with [phpspec](http://www.phpspec.net/en/latest/). ## Author [Antonio Carlos Ribeiro](http://twitter.com/iantonioribeiro) ## License Google2FA is licensed under the MIT License - see the [LICENSE](LICENSE) file for details ## Contributing Pull requests and issues are more than welcome. google2fa-laravel/tests/Google2FaLaravelTest.php000064400000017035152665373430015650 0ustar00createFromBase( \Symfony\Component\HttpFoundation\Request::create( '/', 'GET' ) ); } protected function getPackageProviders($app) { return [ \PragmaRX\Google2FALaravel\ServiceProvider::class, \Illuminate\Auth\AuthServiceProvider::class, ]; } public function setUp(): void { parent::setup(); $this->app->make('Illuminate\Contracts\Http\Kernel') ->pushMiddleware('Illuminate\Session\Middleware\StartSession') ->pushMiddleware('Illuminate\View\Middleware\ShareErrorsFromSession::class'); View::addLocation(__DIR__.'/views'); $this->loginUser(); } protected function getPackageAliases($app) { return [ 'Google2FA' => \PragmaRX\Google2FALaravel\Facade::class, 'Auth' => \Illuminate\Support\Facades\Auth::class, ]; } protected function home() { return $this->call('GET', 'home')->getContent(); } private function loginUser() { $user = new User(); $user->username = 'foo'; $user->google2fa_secret = Constants::SECRET; Auth::login($user); } protected function assertLogin($password = null, $message = 'google2fa passed') { config(['google2fa.error_messages.wrong_otp' => self::WRONG_OTP_ERROR_MESSAGE]); $renderedView = $this->call('POST', 'login', ['one_time_password' => $password])->getContent(); $this->assertStringContainsString( $message, $renderedView ); if ($message !== self::WRONG_OTP_ERROR_MESSAGE) { $this->assertStringNotContainsString( self::WRONG_OTP_ERROR_MESSAGE, $renderedView ); } } protected function getEnvironmentSetUp($app) { config(['app.debug' => true]); $app['router']->get('home', ['as' => 'home', 'uses' => function () { return 'we are home'; }])->middleware(\PragmaRX\Google2FALaravel\Middleware::class); $app['router']->post('login', ['as' => 'login.post', 'uses' => function () { return 'google2fa passed'; }])->middleware(\PragmaRX\Google2FALaravel\Middleware::class); $app['router']->post('logout', ['as' => 'logout.post', 'uses' => function () { Google2FA::logout(); }]); } public function getOTP() { return Google2FA::getCurrentOtp(Auth::user()->google2fa_secret); } // --------------------------------------------- tests public function testCanInstantiate() { $this->assertEquals(16, strlen(Google2FA::generateSecretKey())); } public function testIsActivated() { $this->assertTrue(Google2FA::isActivated()); } public function testVerifyGoogle2FA() { $this->assertFalse(Google2FA::verifyGoogle2FA(Auth::user()->google2fa_secret, '000000')); } public function testRedirectToGoogle2FAView() { $this->assertStringContainsString( 'google2fa view', $this->home() ); } public function testGoogle2FAPostPasses() { $this->assertLogin($this->getOTP()); $this->assertStringContainsString( 'we are home', $this->home() ); } public function testWrongOTP() { $this->assertLogin('9999999', self::WRONG_OTP_ERROR_MESSAGE); } public function testLogout() { $this->assertStringContainsString( 'google2fa view', $this->home() ); $this->assertLogin($this->getOTP()); $this->assertStringContainsString( 'we are home', $this->home() ); $this->assertStringContainsString( '', $this->call('POST', 'logout')->getContent() ); $this->assertStringContainsString( 'google2fa view', $this->home() ); } public function testLogin() { $this->startSession(); $request = $this->createEmptyRequest(); $request->setLaravelSession($this->app['session.store']); $authenticator = app(\PragmaRX\Google2FALaravel\Google2FA::class)->boot($request); $authenticator->login(); $this->assertTrue($request->getSession()->get('google2fa.auth_passed')); } public function testOldPasswords() { config(['google2fa.forbid_old_passwords' => true]); $this->assertStringContainsString( 'google2fa view', $this->home() ); $this->assertLogin($this->getOTP()); $this->assertStringContainsString( 'we are home', $this->home() ); $this->assertStringContainsString( '', $this->call('POST', 'logout')->getContent() ); $this->assertStringContainsString( 'google2fa view', $this->home() ); } public function testPasswordExpiration() { config(['google2fa.lifetime' => 1]); $this->assertLogin($this->getOTP()); $this->assertStringContainsString( 'we are home', $this->home() ); Carbon::setTestNow(Carbon::now()->addMinutes(3)); $this->assertStringContainsString( 'google2fa view', $this->home() ); } public function testGoogle2FAEmptyPassword() { $this->assertLogin('', $message = config('google2fa.error_messages.cannot_be_empty')); $this->assertLogin(null, $message); } public function testQrcodeInline() { $qrCode = Google2FA::getQRCodeInline('company name', 'email@company.com', Constants::SECRET); $this->assertStringStartsWith( 'assertTrue( strlen($qrCode) > 1024 ); } public function testStateless() { $authenticator = app(Authenticator::class)->bootStateless($this->createEmptyRequest()); $this->assertFalse($authenticator->isAuthenticated()); } public function testViewError() { config([ 'google2fa.error_messages.cannot_be_empty' => self::EMPTY_OTP_ERROR_MESSAGE, ]); $this->assertStringContainsString( self::EMPTY_OTP_ERROR_MESSAGE, $this->call('POST', 'login', ['input_one_time_password_missing' => 'missing'])->getContent() ); } public function testQrCodeBackend() { $this->assertEquals( PackageConstants::QRCODE_IMAGE_BACKEND_SVG, Google2FA::getQRCodeBackend() ); Google2FA::setQRCodeBackend('imagemagick'); $this->assertEquals( PackageConstants::QRCODE_IMAGE_BACKEND_IMAGEMAGICK, Google2FA::getQRCodeBackend() ); } } google2fa-laravel/tests/TestCase.php000064400000000567152665373430013451 0ustar00all() as $message) {{ $message }}
@endforeach google2fa-laravel/tests/Constants.php000064400000011072152665373430013703 0ustar00email; } /** * Route notifications for the Slack channel. * * @return string */ public function routeNotificationForSlack() { return config('services.slack.webhook_url'); } /** * Get the connection of the entity. * * @return string|null */ public function getQueueableConnection() { // TODO: Implement getQueueableConnection() method. } /** * Retrieve the model for a bound value. * * @param mixed $value * @param string|null $field * * @return \Illuminate\Database\Eloquent\Model|null */ public function resolveRouteBinding($value, $field = null) { // TODO: Implement resolveRouteBinding() method. } } google2fa-laravel/tests/.gitkeep000064400000000000152665373430012634 0ustar00google2fa-laravel/tests/bootstrap.php000064400000000125152665373430013741 0ustar00 ./tests ./src google2fa-laravel/src/Exceptions/InvalidSecretKey.php000064400000000165152665373430016703 0ustar00bootStateless($request); if ($authenticator->isAuthenticated()) { return $next($request); } return $authenticator->makeRequestOneTimePasswordResponse(); } } google2fa-laravel/src/Listeners/LoginViaRemember.php000064400000001521152665373430016511 0ustar00registerGoogle2fa($event->user); } } /** * Force register Google2fa login. * * @param User $user */ private function registerGoogle2fa(User $user) { $secret = $user->{Google2FA::config('otp_secret_column')}; if (!is_null($secret) && !empty($secret)) { Google2FA::login(); } } } google2fa-laravel/src/Support/Authenticator.php000064400000007130152665373430015642 0ustar00boot($request); $this->setStateless(); return $this; } /** * Fire login (success or failed). * * @param $succeeded */ private function fireLoginEvent($succeeded) { event( $succeeded ? new LoginSucceeded($this->getUser()) : new LoginFailed($this->getUser()) ); return $succeeded; } /** * Get the OTP from user input. * * @throws InvalidOneTimePassword * * @return mixed */ protected function getOneTimePassword() { $password = $this->getInputOneTimePassword(); if (is_null($password) || empty($password)) { event(new EmptyOneTimePasswordReceived()); if ($this->config('throw_exceptions', true)) { throw new InvalidOneTimePassword(config('google2fa.error_messages.cannot_be_empty')); } } return $password; } /** * Check if the current use is authenticated via OTP. * * @return bool */ public function isAuthenticated() { return $this->canPassWithoutCheckingOTP() || ($this->checkOTP() === Constants::OTP_VALID); } /** * Check if it is already logged in or passable without checking for an OTP. * * @return bool */ protected function canPassWithoutCheckingOTP() { return !$this->isEnabled() || $this->noUserIsAuthenticated() || !$this->isActivated() || $this->twoFactorAuthStillValid(); } /** * Check if the input OTP is valid. Returns one of the possible OTP_STATUS codes: * 'empty', 'valid' or 'invalid'. * * @return string */ protected function checkOTP() { if (!$this->inputHasOneTimePassword() || empty($this->getInputOneTimePassword())) { return Constants::OTP_EMPTY; } $isValid = $this->verifyOneTimePassword(); if ($isValid) { $this->login(); $this->fireLoginEvent($isValid); return Constants::OTP_VALID; } $this->fireLoginEvent($isValid); return Constants::OTP_INVALID; } /** * Verify the OTP. * * @throws InvalidOneTimePassword * * @return mixed */ protected function verifyOneTimePassword() { return $this->verifyAndStoreOneTimePassword($this->getOneTimePassword()); } } google2fa-laravel/src/Support/ErrorBag.php000064400000002314152665373430014532 0ustar00 $message, ]); } /** * Get a message bag with a message for a particular status code. * * @param $statusCode * * @return MessageBag */ protected function getErrorBagForStatusCode($statusCode) { $errorMap = [ SymfonyResponse::HTTP_UNPROCESSABLE_ENTITY => 'google2fa.error_messages.wrong_otp', SymfonyResponse::HTTP_BAD_REQUEST => 'google2fa.error_messages.cannot_be_empty', ]; return $this->createErrorBagForMessage( trans( config( array_key_exists($statusCode, $errorMap) ? $errorMap[$statusCode] : 'google2fa.error_messages.unknown', 'google2fa.error_messages.unknown' ) ) ); } } google2fa-laravel/src/Support/Input.php000064400000001025152665373430014124 0ustar00getRequest()->has($this->config('otp_input')); } protected function getInputOneTimePassword() { return $this->getRequest()->input($this->config('otp_input')); } abstract public function getRequest(); abstract protected function config($string, $children = []); } google2fa-laravel/src/Support/Response.php000064400000005462152665373430014634 0ustar00getErrorBagForStatusCode($statusCode), $statusCode ); } /** * Make the status code, to respond accordingly. * * @return int */ protected function makeStatusCode() { if ($this->getRequest()->isMethod('get') || ($this->checkOTP() === Constants::OTP_VALID)) { return SymfonyResponse::HTTP_OK; } if ($this->checkOTP() === Constants::OTP_EMPTY) { return SymfonyResponse::HTTP_BAD_REQUEST; } return SymfonyResponse::HTTP_UNPROCESSABLE_ENTITY; } /** * Make a web response. * * @param $statusCode * * @return \Illuminate\Http\Response */ protected function makeHtmlResponse($statusCode) { $view = $this->getView(); if ($statusCode !== SymfonyResponse::HTTP_OK) { $view->withErrors($this->getErrorBagForStatusCode($statusCode)); } return new IlluminateHtmlResponse($view, $statusCode); } /** * Create a response to request the OTP. * * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function makeRequestOneTimePasswordResponse() { event( app()->version() < '5.4' ? new OneTimePasswordRequested53($this->getUser()) : new OneTimePasswordRequested($this->getUser()) ); $expectJson = app()->version() < '5.4' ? $this->getRequest()->wantsJson() : $this->getRequest()->expectsJson(); return $expectJson ? $this->makeJsonResponse($this->makeStatusCode()) : $this->makeHtmlResponse($this->makeStatusCode()); } /** * Get the OTP view. * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ private function getView() { return view($this->config('view')); } abstract protected function getErrorBagForStatusCode($statusCode); abstract protected function inputHasOneTimePassword(); abstract public function checkOTP(); abstract protected function getUser(); abstract public function getRequest(); abstract protected function config($string, $children = []); } google2fa-laravel/src/Support/Request.php000064400000001032152665373430014453 0ustar00request; } /** * Set the request property. * * @param mixed $request * * @return $this */ public function setRequest($request) { $this->request = $request; return $this; } } google2fa-laravel/src/Support/Config.php000064400000001072152665373430014234 0ustar00auth)) { $this->auth = app($this->config('auth')); if (!empty($this->config('guard'))) { $this->auth = app($this->config('auth'))->guard($this->config('guard')); } } return $this->auth; } /** * Get the current user. * * @return mixed */ protected function getUser() { return $this->getAuth()->user(); } abstract protected function config($string, $children = []); } google2fa-laravel/src/Support/Session.php000064400000003543152665373430014457 0ustar00config('session_var').(is_null($name) || empty($name) ? '' : '.'.$name); } /** * Get a session var value. * * @param null $var * * @return mixed */ public function sessionGet($var = null, $default = null) { if ($this->stateless) { return $default; } return $this->getRequest()->session()->get( $this->makeSessionVarName($var), $default ); } /** * Put a var value to the current session. * * @param $var * @param $value * * @return mixed */ protected function sessionPut($var, $value) { if ($this->stateless) { return $value; } $this->getRequest()->session()->put( $this->makeSessionVarName($var), $value ); return $value; } /** * Forget a session var. * * @param null $var */ protected function sessionForget($var = null) { if ($this->stateless) { return; } $this->getRequest()->session()->forget( $this->makeSessionVarName($var) ); } /** * @param mixed $stateless * * @return Authenticator */ public function setStateless($stateless = true) { $this->stateless = $stateless; return $this; } abstract protected function config($string, $children = []); abstract public function getRequest(); } google2fa-laravel/src/Events/LoginSucceeded.php000064400000000726152665373430015501 0ustar00user = $user; } } google2fa-laravel/src/Events/EmptyOneTimePasswordReceived.php000064400000000462152665373430020372 0ustar00user = $user; } } google2fa-laravel/src/Events/OneTimePasswordRequested53.php000064400000000742152665373430017737 0ustar00user = $user; } } google2fa-laravel/src/Events/OneTimePasswordRequested.php000064400000000740152665373430017565 0ustar00user = $user; } } google2fa-laravel/src/Events/LoggedOut.php000064400000000721152665373430014510 0ustar00user = $user; } } google2fa-laravel/src/Events/OneTimePasswordExpired.php000064400000000736152665373430017231 0ustar00user = $user; } } google2fa-laravel/src/config/config.php000064400000003321152665373430014064 0ustar00 env('OTP_ENABLED', true), /* * Lifetime in minutes. * * In case you need your users to be asked for a new one time passwords from time to time. */ 'lifetime' => env('OTP_LIFETIME', 0), // 0 = eternal /* * Renew lifetime at every new request. */ 'keep_alive' => env('OTP_KEEP_ALIVE', true), /* * Auth container binding. */ 'auth' => 'auth', /* * Guard. */ 'guard' => '', /* * 2FA verified session var. */ 'session_var' => 'google2fa', /* * One Time Password request input name. */ 'otp_input' => 'one_time_password', /* * One Time Password Window. */ 'window' => 1, /* * Forbid user to reuse One Time Passwords. */ 'forbid_old_passwords' => false, /* * User's table column for google2fa secret. */ 'otp_secret_column' => 'google2fa_secret', /* * One Time Password View. */ 'view' => 'google2fa.index', /* * One Time Password error message. */ 'error_messages' => [ 'wrong_otp' => "The 'One Time Password' typed was wrong.", 'cannot_be_empty' => 'One Time Password cannot be empty.', 'unknown' => 'An unknown error has occurred. Please try again.', ], /* * Throw exceptions or just fire events? */ 'throw_exceptions' => env('OTP_THROW_EXCEPTION', true), /* * Which image backend to use for generating QR codes? * * Supports imagemagick, svg and eps */ 'qrcode_image_backend' => \PragmaRX\Google2FALaravel\Support\Constants::QRCODE_IMAGE_BACKEND_SVG, ]; google2fa-laravel/src/config/.gitkeep000064400000000000152665373430013526 0ustar00google2fa-laravel/src/Middleware.php000064400000000657152665373430013440 0ustar00boot($request); if ($authenticator->isAuthenticated()) { return $next($request); } return $authenticator->makeRequestOneTimePasswordResponse(); } } google2fa-laravel/src/ServiceProvider.php000064400000001663152665373430014474 0ustar00publishes([ __DIR__.'/config/config.php' => config_path('google2fa.php'), ]); } /** * Merge configuration. */ private function mergeConfig() { $this->mergeConfigFrom( __DIR__.'/config/config.php', 'google2fa' ); } /** * Register the service provider. * * @return void */ public function register() { $this->app->singleton('pragmarx.google2fa', function ($app) { return $app->make(Google2FA::class); }); } public function boot() { $this->configurePaths(); $this->mergeConfig(); } } google2fa-laravel/src/Google2FA.php000064400000014527152665373430013071 0ustar00getQRCodeBackend()) { case Constants::QRCODE_IMAGE_BACKEND_SVG: return new \BaconQrCode\Renderer\Image\SvgImageBackEnd(); case Constants::QRCODE_IMAGE_BACKEND_EPS: return new \BaconQrCode\Renderer\Image\EpsImageBackEnd(); case Constants::QRCODE_IMAGE_BACKEND_IMAGEMAGICK: default: return null; } } /** * Set the QRCode Backend. * * @param string $qrCodeBackend * * @return self */ public function setQrCodeBackend(string $qrCodeBackend) { $this->qrCodeBackend = $qrCodeBackend; return $this; } /** * Authenticator constructor. * * @param IlluminateRequest $request */ public function __construct(IlluminateRequest $request) { $this->boot($request); parent::__construct(null, $this->getImageBackend()); } /** * Authenticator boot. * * @param $request * * @return Google2FA */ public function boot($request) { $this->setRequest($request); $this->setWindow($this->config('window')); return $this; } /** * The QRCode Backend. * * @return mixed */ public function getQRCodeBackend() { return $this->qrCodeBackend ?: $this->config('qrcode_image_backend', Constants::QRCODE_IMAGE_BACKEND_IMAGEMAGICK); } /** * Get the user Google2FA secret. * * @throws InvalidSecretKey * * @return mixed */ protected function getGoogle2FASecretKey() { return $this->getUser()->{$this->config('otp_secret_column')}; } /** * Check if the 2FA is activated for the user. * * @return bool */ public function isActivated() { $secret = $this->getGoogle2FASecretKey(); return !is_null($secret) && !empty($secret); } /** * Store the old OTP timestamp. * * @param $key * * @return mixed */ protected function storeOldTimestamp($key) { return $this->config('forbid_old_passwords') === true ? $this->sessionPut(Constants::SESSION_OTP_TIMESTAMP, $key) : $key; } /** * Get the previous OTP timestamp. * * @return null|mixed */ protected function getOldTimestamp() { return $this->config('forbid_old_passwords') === true ? $this->sessionGet(Constants::SESSION_OTP_TIMESTAMP) : null; } /** * Keep this OTP session alive. */ protected function keepAlive() { if ($this->config('keep_alive')) { $this->updateCurrentAuthTime(); } } /** * Get minutes since last activity. * * @return int */ protected function minutesSinceLastActivity() { return Carbon::now()->diffInMinutes( $this->sessionGet(Constants::SESSION_AUTH_TIME), true ); } /** * Check if no user is authenticated using OTP. * * @return bool */ protected function noUserIsAuthenticated() { return is_null($this->getUser()); } /** * Check if OTP has expired. * * @return bool */ protected function passwordExpired() { if (($minutes = $this->config('lifetime')) !== 0 && $this->minutesSinceLastActivity() > $minutes) { event(new OneTimePasswordExpired($this->getUser())); $this->logout(); return true; } $this->keepAlive(); return false; } /** * Verifies, in the current session, if a 2fa check has already passed. * * @return bool */ protected function twoFactorAuthStillValid() { return (bool) $this->sessionGet(Constants::SESSION_AUTH_PASSED, false) && !$this->passwordExpired(); } /** * Check if the module is enabled. * * @return mixed */ protected function isEnabled() { return $this->config('enabled'); } /** * Set current auth as valid. */ public function login() { $this->sessionPut(Constants::SESSION_AUTH_PASSED, true); $this->updateCurrentAuthTime(); } /** * OTP logout. */ public function logout() { $user = $this->getUser(); $this->sessionForget(); event(new LoggedOut($user)); } /** * Update the current auth time. */ protected function updateCurrentAuthTime() { $this->sessionPut(Constants::SESSION_AUTH_TIME, Carbon::now()->toIso8601String()); } /** * Verify the OTP. * * @param $secret * @param $one_time_password * * @return mixed */ public function verifyGoogle2FA($secret, $one_time_password) { return $this->verifyKey( $secret, $one_time_password, $this->getWindow(), null, // $timestamp $this->getOldTimestamp() ?: null ); } /** * Verify the OTP and store the timestamp. * * @param $one_time_password * * @return mixed */ protected function verifyAndStoreOneTimePassword($one_time_password) { return $this->storeOldTimestamp( $this->verifyGoogle2FA( $this->getGoogle2FASecretKey(), $one_time_password ) ); } } google2fa-laravel/src/Facade.php000064400000001112152665373430012511 0ustar00logout(); } } google2fa-laravel/CHANGELOG.md000064400000003235152665373430011667 0ustar00## Change Log ## [2.0.1] - 2021-08-15 ### Added - Make image backend optional if bacon is not installed ## [2.0.0] - 2021-08-15 ### Added - Allow Google2FA QRCode 3.0 ## [1.4.1] - 2020-09-20 ### Added - Improve PHP 8 compatibility by not defaulting to imagemagick - Make the code PHP 7.0 compatible - Supports using a different guard - Ability to setWindow dynamically ## [1.4.0] - 2020-09-20 ### Added - Laravel 8 support ## [1.1.2] - 2019-03-13 ### Added - New config item for empty OTP error messages: 'cannot_be_empty' => 'One Time Password cannot be empty.', - Tests to check for HTML error messages ## [1.1.1] - 2019-03-12 ### Removed - PHP 7.0 support ## [1.1.0] - 2019-09-10 ### Added - Laravel 6 support ## [1.0.0] - 2019-03-21 - Start using Google2FA QRCode as base class - Support QRCode generation again - Support API / Stateless requests ## [0.3.0] - 2019-03-20 - Upgrade to GoogleFA 5.0 ## [0.2.1] - 2019-03-20 - Removed QRCode generation via Google2FA 4.0 ## [0.2.0] - 2018-03-07 ### Add - Firing events: - EmptyOneTimePasswordReceived - LoggedOut - LoginFailed - LoginSucceeded - OneTimePasswordExpired - OneTimePasswordRequested ### Changed - Google2FA is not dependent from Middleware anymore - Small refactor - Change license to MIT - Move from phpspec tests to PHPUnit and Orchestra ### Removed - Support for PHP 5.4-5.6 (should still work, but would have to be tested by users) ## [0.1.4] - 2017-12-05 ### Add - Support Laravel 5.2+ ## [0.1.2] - 2016-06-22 ### Fixed - Fix middleware returning nulls ## [0.1.1] - 2016-06-22 ### Fixed - Keepalive time not being updated correctly ## [0.1.0] - 2016-06-21 ### Added - First version google2fa-laravel/composer.json000064400000003103152665373430012572 0ustar00{ "name": "pragmarx/google2fa-laravel", "description": "A One Time Password Authentication package, compatible with Google Authenticator.", "keywords": ["authentication", "two factor authentication", "google2fa", "laravel"], "license": "MIT", "authors": [ { "name": "Antonio Carlos Ribeiro", "email": "acr@antoniocarlosribeiro.com", "role": "Creator & Designer" } ], "require": { "php": ">=7.0", "laravel/framework": "^5.4.36|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "pragmarx/google2fa-qrcode": "^1.0|^2.0|^3.0" }, "require-dev": { "phpunit/phpunit": "~5|~6|~7|~8|~9|~10|~11", "orchestra/testbench": "3.4.*|3.5.*|3.6.*|3.7.*|4.*|5.*|6.*|7.*|8.*|9.*|10.*", "bacon/bacon-qr-code": "^2.0" }, "autoload": { "psr-4": { "PragmaRX\\Google2FALaravel\\": "src/", "PragmaRX\\Google2FALaravel\\Tests\\": "tests/" } }, "extra": { "component": "package", "frameworks": ["Laravel"], "branch-alias": { "dev-master": "0.2-dev" }, "laravel": { "providers": [ "PragmaRX\\Google2FALaravel\\ServiceProvider" ], "aliases": { "Google2FA": "PragmaRX\\Google2FALaravel\\Facade" } } }, "suggest": { "bacon/bacon-qr-code": "Required to generate inline QR Codes.", "pragmarx/recovery": "Generate recovery codes." }, "minimum-stability": "dev", "prefer-stable": true } google2fa-laravel/.gitignore000064400000000116152665373430012041 0ustar00.DS_Store composer.phar composer.lock vendor coverage/* .phpunit.result.cache google2fa-laravel/docs/middleware.jpg000064400000143422152665373430013630 0ustar00ÿØÿàJFIFÿÛC  !"$"$ÿÛCÿ �"ÿÄÿÄÿÚ öP¼“ÕçØ[Vd=^WRGáõß²úŒÓ=Ó©qÉ×ÁÏuŠŸ«ó·ÓVÃåôꔜzj”ioæeÕ9:ùï’£’‡èxµ—Ø MÚ†/£JøÌgZ¥_Ô¶L¥æ³Þâã͹CÉ�X¨~:ãBç©Æ¯•›Åµ§”z¿L“Ò•úÇ�}OŸëù¾¼G.½5zúo† ¾Lt®áµúõyý¯›§óŸj�;¢Îý¿¶ÄËA¦§×{<´´»L²MÙ[{7—õÏ9ôo'|–+k~Ymf¾³ÍßQ'ßÕù÷TšH|ݾ(¹5Ýy×ÞT}ðíÍ•³¹öyúX¯RóvÊOûÐ�ÔÙYY™Ûd÷{y—²øç±æ‡Éú@>Řk� ÓÂ*«§µ]]c#ѦväoG/»xŠªègì{Úœ×'¶jUÚ·_ódÔ|ý9ï.¹êóÑSíRçæºcX�gZÊ+>¦7›Ò •øÖºóã‚Íǯ&On霶¤Å}qvùünx²XmOmíÄq˶êòγkkâ››6lˆ¤­ý±/Ä?GeÍÙ%€Póçì œUÚ$òG FHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŒHŽ@rFIÏÑÆIÑQwËËÔK-?Áw %©÷ÓWÆh?Eâ†Rç⛨² þƒ2c7¾eèÆŒÉ€äø>ãÏ¿‘ù'Àú|�‰¾Ô2þ¾‡Ü_@ ‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰„Ÿ`Vpöå¡g–hYá¡g†…žxhYá¡g»¥³z€xÞwE�ý§äË.Íf…e¡Î±‹_­f¡¡ûγk�»)Z¢¿E�ÑKìƒñ¬ñ¼î‹;ûOÉêo¼á˦Ú|_FìòÖ.ªïÎ�1½´òç>ž‘W‹k-wEß—²Åþ°'Qèog“φÞ|ñècφ<ñècφ<ñècÏ-õŒh<~°"ñ[[9‹Ò¯ü,U°(xKE?én­ù-ŸE‹ƒð°QÊ–êÞ…ê}ऒÜVÇl3÷“æôrUßþ%_&€¹þ}ÙO%¸¨îé}6b“»·ðý ŸŒâž¸�\ݶüÉÇûWXÕ–2|Mgļ•Òè “˜’8ìRI¹ºT¥Ò£ä¹Vó%Ú¢"ñÅÁ-⻞®Ts¥ªª%ºTqG:Z©þí]Àš\‹`¤‚4Jµ–Š/žPu%ª¶¸V 77HªÔU~Ú î ží´-Oåº):,Úœ•·©j¾¬ÇÈ©ìêR\Ž>+’U}Ù%­æ»Ugù*¹oËOÝÔJîKÂÔüÜ »@<ÃlØÁë\^ægõ+Ìó'¹fi¼È÷6f”Ù¼š”÷7†zis§ÆfZâóû£±Œ=?’ì͘1˜Íž0ñŸfÚmŸaäÚlÍѦ⦹8¸öyƒO˜qœ^�ŒÓ´Þ€<—Ð;E/Œû0lñ›3f lÍÏŸ›>;ž"š—·ÏÇ i°ýäÛ1ÅÛLvvùÿî\t¼FŸų<›Ó1˜Ãú—3Ú\úoóÿô`6fçÆK­ý7™sˆô Zla¦ô Çi¦Àoü¹õ¯%ÓžÍÅKægn3§¡LìôÓÆ}›Ì½4¹3Æ}ÏÆOsþþ™ÆÌÈìó?@âÛ“·É¶b—Ð8³&š›g䧦ÜÓfLe/¹ã.Ûšc·Ü¼7ÙŽÀqæ;q†gMçû1ŒÙñ˜ÍŸŸé�fðÍ8¥í¥Ù—^e³ã1›únÓ~ðÞÃÙ°ÍùÛ™¹Ìž�¦ó?rãóÿ@ñ“g˜m7aè/38¸»x‹«œÎ˜c4ô§ yÿ fO2ºÙö}ž›üþló4Û3‹Üü—Ð 0 ŸŒ—9Ÿ@e(ðßf¥Æk®/&=ýMäÇgeѦvÒž�MŒ¥=g�³ŒÌqÜÓžµüÍîFÌ~3î^Lfni¸…Ö2äÓSRìË®ß%ºŸY¥8é}”Æ]\Œ›fyŸ¦]lÌe/o†�ë>3îeÈ8üËÐ3'’úoiŒ¹ÙÓ]·>Ll鸮K¯2ãß�ž3´Óqã8†gÓ3%+Ð8Ž:]7iŒþŒðßY.@ŒÆlñ�ã>Ìløû™éƒÌôÞ™™M9˜ÆlögneÚc){x‹®Í�1K™ô 1æ~³ŒÓ`c1žµ˜1�˜ãó/s¥)šnÒ—3é£ùÿ‹úˆþsõ�0À=˜9vû6˜ðÞÏfgKìÃÌýŠäÿÄ7&`56$%02Fp !G"@� 34€#ÿÚÿ™×�m–×?• !Ÿ�Y,Š×7\Üe&ôÃù%’È­sq”‰˜Õª×²­þȉ#Ü’O=+*Äz›Ž¼s‘‡$ˆËLGݦgcÜrŠ™uƒ&!Š£„hÆ%!ƒ z8s�Ô`A5˜äߕʹü~åË5�• źâmSñöP¤²SU•ü¯ŒÀå¡h,Aå©“%ÁÁæãÞZÖ”!Éøô¨Å2�8`ì‰øû©ãhPÊ`¶ß” ’u 2£™c%ÁbÌ‚BÉy±Ù\¸ ‰]Ÿ�Þ]ÖÞ›÷ž>^)áyJÉ|8x‘ã`´a@CŽ—KqœZ1)f[£h ÝóÄ©ï”aßø¤~_†þ¦GISùx¬ÇãQú!,$L‹-!W%”ãèë 5Y_ÊâåÜs%Ì=¸÷…hàD~qqм�šæ/üJX�ޝÈmtM¹ÁÀ±ºDb/jñ{’ìá›À�´~1�ƲJ28ƨQ¦=ƒo�¡ >,0M X½Ϲ K‘,Jëyš  Ä¸˜þú³ýWÚS¸œ qO�"¸a�6@Õ=òŒ;ÿ�ËðßÕõFQòœ_åßSå�)'0Ä ÌG9ô­e+Ä~[R«[QÐ ú²=4‰ 2!¤XÕMHJ1cäÀ‡°¸r—¬–ñóÁï�6§ ©Á÷‡ÅŽe¶²sظ­²¦q,CÿC,iHÀCˆÞnγÀ$'¶(èb1ðÜpQsà[$xØö@KÍÙÖccXãÄŒÉçŠÙƒ€+aŽDPïœPíÍñÁv�pQR6kÁ4 «YIwKíð¼€¢ÇW1Án¨ø�\Œ`ÇRq±¼D‘Y’‹÷a (‚“FAC‚ÀÒÌ @²Xì”Ïm�·œÿàÊDóD2ì€Mbǹt9C�µ©(Eä�°¶¿�¿ ²‡ ™‹Uí{^ÞþÎ~[‰½qÐ…8DÊ M²�e&¾>u‡eroFôaòd6øò‡ Ö'\\:ço!+¼l‘·•j`Å¢RT÷ ¤¹óŸ–‘ øðR½ +’“B²|Éi‘´´ÊOŠ”-R ÊKÈTŸ"Ǿ3Ÿ–åMº)Òctoùww.„•j(HöŸ:9¹ÖmGѳc¡™x„üÉ#8œŠã8¬Šyï Ø—‚k$Óâ0mG„¬Q¦ì>sò×rvTÒ¡ËN5uÃ,t7åeJúÕ ù¹d,«q"âm={ûóÂß xü-ý¾7µ¯^ø^Ö¿ôø[Çã{ZÿÎe<¶©o–‹ Kd_óJ&ÌRŸ9 eÄ<×ô4íœ_ÀÃ3¿J[·÷·Æî&Ï2¿19d¹ýwwÀ—œKM³C›:·5{àóž_²äUt:¢I½œ]§ßv^Pê)/8 Øò<Ö†bŸó˜y+(ÕÙA�‹|5—}ì@Œ¾ª xõ.ã5k^Qç¼µYVyOµÑPÌÜÄ]Âì€ËBH³d©ðÙU%Á Pä©àÏUÒ:†Cm \~±¢á-5HW–ìAh[Ä)"©—ÓqT–Zê©–®eÉQ,¤fÂMS=_œ«4H·`K!FËKb^a¤º6¸§áNø—e‹–)$G¾×Y%Pàè4õ)²œeO)c²âkCŒ�«uVºØ£Ÿu'ȾòIP �æH<3[Q‰u·ä�ë„�ÏF›î2BT±G¹lóÍ�ç»Ê²¥8±ˆmKzŠõó…�Ø÷|K2‰]ä¸[ã,¡X /%·’ó&*Ïô{å´ G)…nQ„…6(hSm”ÓÉ!D‘{2!CÙΠµ1Ô—võ)Ö¸Œ‚V²ˆ¹žwJÈí4É‚ Q„Ùô?µ¿@Í’Ÿ±e&ß¡¦ßbÉ`–níË*™p¦Æ2Ã~xŠ»=z·œ1~Y®ÃÍó…�ÊRðo�Ô8A¨rÄ<£&Kåí>kl´ÃâºõŽ ‡!јêæÅSÖ e¬%`(ƒTØ#\u±rL{d¥Éwž¹Y«�%ìÑ.¨u#*‹‡Ò9ÄǶ¶Åö…ÿ½›M��É}¦ßjÀ³·û—‡iå­ _õ:„¸ßý3WZ"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ¢+DVˆ­Z"´EhŠÑ�ÍWê§ÕM»²¾;t]µmñuzR]ÿ_õYVºýÛ:§ÃÉk R­eZÌ3kY†l›4Ý“áo4Õ–¦Ð¤!E½äŸÕùŠ­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzÚõµëkÖ×­¯[^¶½mzOòÑgˆ"¹¸Êæã+›Œ®n2¹¸Êæã+›Œ®n2¹¸Êæã+›Œ®n2¹¸Êæã(CÄ-_ÀY'Î;ùÏðI󪌫0Øû ð «¹»)Qæ$•Æ—‘oP\mÇCñ‡2Òâdƒb efHŸ oç_ÀY'Ϊ%æC~¼¶Ã�=Æ“` , –Y¸/6˜ë<8Î)¶Ú%ï"èñÒDˈLuc:þÉ>uùØßο€¥aPi=³]³]³]³]³]³]³]³]³]³]³]³]³]³QP¨Ÿú‡­R’‡úÄÚ¬e¯f^ó.R®�®U¶¹iµ\ÄÚš7ý +j×ø¤+FˆRSÕ¦ôƒPºëSãÕ¦ö ÿ>�KVW\ÚVéŠJ*íXU*í{è«+À†Ü&ˆê h¦—z .%o\…4ó/)¹-µ~£§oªm‘[»iZX…0µ-Á×b˜³Œ¾ÕŸ[±Ì¸Ú¼d�ÚqÊeXî\“qJMÞQ"_dø[âëM;d%(OõÚÖµ¿ú…÷lÒ.m¬E˲iÓkX»mç­Ë6¥ ¦JºÖãåhƒjlÔ¹I5 M‰½ÒÃètkšëjë“L¹æ£ø«/À¦É!å2¥^È{ÀVÖ—T‚[ºÝ› ¿9m¨l:Ú¼µôÊmÛ2–ˆÙÛ-áݹ/ Ž –®‹îÊn•ÿÁcäß©`»l²®§”Ñ Ù¥8ë¬ùŽ\E¼§IwÉ¡ÉÝ :¥Š+�<†Ý)M:—˜KHyÆÚ)HºS•Ô‘uÝÍêÊ«ºˆCdºóM¶S~7&Ë'Ͳî-‹<òjĹà3Î]”õXB]Y/º‡y(±w}7·šó¾c̼˫)m· yWJýÙà¶ •¶§ÙsÏ- %#‹o5æ—SÊ»¾{ÊwÎ"ÎŽíéÆT¤’…ÞÅ,·lÚßm»�~‘å:‡i}JXq³GEÐ�ìíÜwÎS#7t'G-HY¥•4BÆÚ8Ÿ<”ÊÔÒÐòî¶–ê\aåˆG˜û6Eú¯ÿV^[ %ǘ]—Òº‡,§�sþ M)€†îˆ*îˆ*îˆ*¦ ”“ø›=< mw'‹•,ÁÁ£{èÒ˜oÀªo(;“ z$Ø)¹H.Øü ¶1r˜ t"˜7íÑbB&nSÔá ù¤ò€ò|¦'ÆMÅÐEK4$\í~]¯\\-<Á³¦ÏD…êɨÎ,îÄÅÅ|(/}e¾lED…7P‘p]±7Û@°nÅ�É…’HEK:l NÍÊT$\lMÅÁvÆ.,±£MÅ� &Y™£T°S½Ñ;FÏK4$\lMÅÁvÀEd�AbóÒÆÎûìÒ˜oIÍIå° )Ûr�]±‹ÏD…±&’lôHDšSææ3r“µÅƒØ�’�]±7)Û’“µ7×v Ed�ASæ’ ±£ÀXt$¤lMÊAvÀBä†ÁBE� &L7¾�ƒF6X)Ù¹OLBEÁvÀP,;Úþ§å ¡jn,é©0 r@‰›‹� …É ‚íLÀXv.+á÷é©< Y`† ƒpèNv¾ã57êp…`)ÓJ–nSšÆ1q_ ßf•,îP,I°P�w Ðñ“}wh²ÆÎ„,°Xt$¤ïsæÒ€ñ˜OÓr•7);A,hÁøPA ’HED„NPS� ø.0XÙ³³q~§4YcF„à¡h"˜4o}S ”,hظ¬‡V6ß¶?íƒE|( §²CI4W‚ûôÔŸß¡p@Š–6w÷<'Cßs}'ëº4¦)ós¹H*\l!�)óI4©`†4YcFÂ~˜÷Ñ¢°hÙ@²Á ÝœgàTlóá‹ÏK;7)ê~,îO”Œ\l!¦åš“Y`§B�–6tÑ_ 1üw¹ùL³“4Ycg~ý &h²ÆŒ¯›öv¬‡TI°\^YÆbâ¾¾ò�rCIõd,`B¾i&ŠÁ³¡@ä�ûŸñÚ›‹�®PÄÂbÁã?ÉÜ¡Ý÷7;Üð‘s½Ï7;ÜáøYŽP,I°X¸¬‡+ ›JÆSáöHi0�wï£J`!�+$4™¹IÚ„è{‹;“õÝù°P’€ÂÆSæ’ ÎÍÊ  )êy¹H*„”ôÄܧ¦*XÜ:o¡ã*XѹIØZî‰Ú¬lÑ��|Òqq_ 1÷ÜßCÆ=�„1¥a†“Êb|gíˆNìäæå(Ø�Òa:îO”ár| ¦ �„‹‚퉸¸.ØÅʉ6 (X!¡;³ŒX“IûŒ.w” l$\lMÅÁvÀE>ÙØE0hÐ�wï¬ÛéˆH¸.Øô%MÅÁvÆôÁ¥K;7×raO>l¥K; tÔžQ$¤lMÊAvÄ$\í+æÁMÊ ‹‹,lîP+ææ&”ÀXt$¤lMÊAvÀBä†Á+ ×qžú;˜ÿ×øÇ úc6ú`!_7ìï$"fâà¨Ùè� ¡9Ùª6oBT$¤r‡wÞQÁ£z²3)ós¸¸*âñ>3‹ÄøÀЉ o¡ã1rŸ6 ßY·Ó’�]±èJ›”‚íŒ\¦ò‰çÍ' ”Œ›èy?ÛÀÄ›Y DÍÊNÔ'CÉ„V&‹,lé¥fa ÊNÍPP9 Dš.fhБsµÅ�ÉÍÅÎÔ'CÆ+ ï£E`Ñ»^ »^ »^ ¸°xÎׂ  bB'‹“4V !Xn,LØ“Iíx*íx*VÑX4` bB'úx°y?úùÿÿÄ; !1AR2PQq�‘±ð "3`pÑ#4B¡0a€� ÁáÿÚ?úçhŸRÚïN–Õo¸ !3¹¦JÇóMVº;×ob�Zaštö¦ÈA�jkå¡ôõÔŒñ ܺ:ݽŠ|¬g8ѨSM(˜G2Þ™<­”G(õ#<@й>F3œhµ�»z¸&Ͳ+XÀ/W\¥Qž0nÞÅ:F·2Œñ�Rå ÆG¸n%n÷K$Ü ´Z#œsEŒ,óO'}Îv¶oÒî™[¢ªoÍGÞ­˜‹½C]¤Ž´øÚ,�4ê^ñ´¿.õak¨w«C\ë[CM0û¦Y‹_­‘Õ!9s@÷E·œÂÒ/S"¿ Ñ9§ÝÇ´&{³´HÑSÔƒ ÆÍÕ_ø s¦&A¹´ïQ¶Œ“観Y$AýJ&3ixwr°ÜÖIs.’"¸Û,-5 E�$8îWzþõ²Ã[×t4¸;z,i!Çr0͘[v˜)!ŽNpMhh£V­·¯oѲÃ�ÔlÑtµc ¹Lvxã5hB6‡_ÈÛ£B6hK¯«�½{z’äçÈ™4tÅpÐ3ùˆâ‡^Š`W^Ši¢8ôþ �èû5¿CN(d½yiݧr+qÐ9ÁnŠ{ýÿØÐÏM ‚Ú!â+h‡ˆx­¢!ⶈx‡ŠÚ!â+h‡ˆx¡3»J9ͼ,ò8 \æ_/Y¡K1ØÒµypãÃ’Ž$ê ¢ MŠ‚š;-öîRSBèL°“—Å i‹qa6QÃ$�AuÍ? fŸO+2sPŠBì fƒ[ŠÙ-Zl8°›&D÷õBÓ:à7b©§1‡yûË“½¶IÚB£¦”Îܶ#7±;Ø~ ‡câXíRzÄü2²}±(~Ã'è©þÇ/ÉM+ÅDM,”R½Õr4œ¬UÚÚXöü¾+”�Þ2±²§{DKÛ|ÓêùÆó1·)—A®.'À&¸¶7´áÅ´/­dÁãÚ»{�”¹Ó—Æã`v^Ø�¬ñaÿjf6ÈN÷_åtçTkÖÝþ¤æ¡”ÇÅú*‡Ë«FX{û×)bÁ=¶ûÈÓpŸ[;Æä$pih9Î; ä�då¸qh¸3òBG–ƒ‘Fi ƒ‰Ì!3Ë�̨ªe‹&'½Ï8œ¹×àÁ|´kÕiµs4—mF¢Rüx³RÔË(³ÜŒ¯- '%$¯”Ýæë\œ7%Î87òQT˳¤™òuÍþøµÝmËM»2C$sË@6!n«éºvì!¤oõñCrí×ä·iõæŽÄëzùéÜ·hÜ4wè;Ûô†Žåë×çý ›O+…ÃO‚Õgà> UŸ€ø-V~àµYø‚Õgà> UŸ€ø'SÊÑrÓáØ*_pÎàŒÌkðú˜£6%sÑñ¬Å{-b. ™¢ ÍþôV,¸ÃÒI‡¸ ®)’gbV-iÎ_šX/¹¹zëk\!×›Þ\)…JW¯äªR‹š+–“j‡U懽àá–W]Ò¿B Ö¨¼ÑmÎSη�¹I*ˆá¶ÍPüÒJVÇ S5X¢+ŠœPcxÈcî˜�N¾$¤ŽSô$ì)‰VеE‡˜U¨‚¢�L+f⑦h)X·xMªùéHS2AÍV(ŠáúPawT“1\~媹'CÎåôxAÑ›aÅOïçïåxÖ¥QªÑïTnçXøO=R© 5%>*"µÝ¢cN¦ø©6¶­8ŒnÛT•s™Tr‡¥’a1D—.t»ª&&ÓNóùå cÊ~•öº88æ=Úf`oD+DV=õ.QhH™CŠkREzR ×ëº (œë ÈË�­�*•ñ–·G œ¢XEj7Š�«ç.ðG’…QôŒ!¤Ëcè5¢ÀËK-À‹U^+�¥ ›".¨™ú¾Øcõá•­œ®Õ�ÜÐ>‹ã¥Ð�³$ ÏGWlQ½Ý9Ëܘý_lLúÒ&?f_Ê&~¯¶öŽ{`¿Y ?Ya¿×nzœ‚uB<‚Ð¥1BqQS¶©m�¤ªŽï›žžçù‰ûUþIî>m­ h°âÏ8—&BEHi™!n©[”>ø–pG²K²�ã�´Ã¾0¡ÛHldžWI!dæäÄ›B¥ä¹z`žeðl©QµÊÝU±(©ë‡•¿ŸY"aiÊr¤�+'æ­£Ò°ãM„EDõAJ¼h u«ÏõhÄÈ—•EÊRJoþÔ‡kìHmúrH{Ríûc‡)±HCEâƒó—vø!'òŽLÈž”¬oÇ9ˆù Ú*ÐÒ‹H=É8·Ò·,y?5ÉèT‰†‰†«•Ûyy$8üŒ£÷¢_™FÖžt)ËB³Òª¸C“g(ǽŒÕo–ÿêÿÊÙû=‘vd�nÈRgΰÐÌ)2C¤¡éâ÷í�–óG¿ík}–øµÿõÒÒÒbO˜!8d‹`ú ¶tûL£–Ü„ÚÃ�JI°DÚâ\È�pélÉfU†ð¹Ï`”ƒvókkƒ þÛØ±g½[mU0 J*DÎûá7ZØ×:ó8 ɹ6}ìY êbZE–žGÚC ³Æ°[8¥eÊetª/%=11#´h  ¾àÊŸ…‡ÙÒ¬îh›ÌÊö÷o4´p~|³ûobÂLËÿü¦·géüa _Ì,}uÄóI…Òôþ‡$gšpl–‰Lãl …3³¥V‘ï4iÏ}#[«-ôR±-ÿÔ/ÿ”378Ó…(ãhŠAÌ´€Y 7M¶ñWÌ×]š�åé�vž�Åc¹~Cß;êSš•þP�ífJÎJ6¸,4Rí8Ô¹¥­Ý^R¯ø' !J¯þ‘Æé¢�Ût×_t0­Åo̹BFÍÅßiWAqŠ7³ß»šòOç’˜ÈѶÕåÁRªöB½+´Ñ] m)«÷žŠ*Þü�·äÚ”h”Íi nLA1b’"ï7tEõóçÂÉÅ;Wá›'”ðõ.KüÖÓDuÄ­£5fïÑDX?…G·f Ž'Žœ{`™WZR±mSUôÃb8L½Q´ÍJÒ¢­QWÕ ½70ù<`‹Èt…¡ d¦¦Ñ›H‘ÇnµI”OZÜ�ÒÉÏ£·µï�åkΕXÞÍL>FU¥®� ¥pÊ(á^Bdq¢Ò%Ì[OV?Žˆ¦åÓôŠ@JËo%À•Âw˜³Å…U`e×l¼EÀ7‘QVœù,{æa×¹kð` �6\ñÜíù\¦”wÆ@¢¯_%R éGÞÆ÷H‘S¦`¦X]”â•0@½0ÙÉí2½šY�åè«NuÏÜœ'¦sàG–•AOï^È–;ù{Ë·�R¨±QUE¼2ýd�¤BO*¢-.x•;Úza É©�|R·‹‹D_ÕÊ�ØÍÎ °n@ÔQÛȽ9Ò )Ô| “Zo¯VÉ:çHBrjcß*•SGVˆ¿«•"R`ÉÔ'%ÔÕÒD¯#Ó-½!E'IÂåZ‡DOÇ—uËkGÇ‘SÑ^x/|í)�|¹„A‰�qû͵ºŽópL ÞyÇP/!lÅ "-+†pì£s uZVœ,UŽ”^1D7 W53R‹f§7MÛÈl]±Iy× bu©GÜ$lœªÑUx®i„8øLÌoÛ;‰ÕTUý\¡Ýá¸,´¨" j5ZUUië€ é[ƒP½jCˆ×íTÒƒ^Iª(Ü^á4èÔ/5*f•_Æ ïgT\S…W_åô»ïï�.庤…èTÊß3¨ÃAKßnÔ°EªóóÁK±4“-(^Ÿ z†9W¦³.ڗ•«ODK$¤Ã¦†J&ŠdcKUyùð‡È„�’§T!ÌÌ?¿$ª¨:¢‚¾„ÊI©±`È+¬STÁ)ëÎ$žqÁ6ʨdD5L±X)×ÚžTòŽ�­ßª¸S¢?88ãMîÆÕ! óÕSÛ ¼ÜÎúWz”[îÀ°ÏŸ@˜£RöïS˜¯Zvg0´Qõ\ehõcjjeæ2uU+é¢óuÀcr»§)kΘǾuî^�HG›(šm×7–’ZKš¥9âU¶ÜÝÜ¥q&t§4$˽ATÞ ¸¦„<ùÇ7´º´“»Q˜Ñs�ÐL$Ëm:*.]v \z ff~çRät�9“]uM•šñÁW×J¤o/_{Ýï{y®¥nëÂå.í´@§ÒÍš|Ç–!LÊ—îª{}Â}¤K ®Kª�K–¥�—RÁ\2Jz(¹Ã­ûÉÇjáj<ª­qª¤8á7SßoTD’†6ÒßZC?6¬+•Çj‰Á(°’å(ëÛ´´ hIÍšç ³ ð†WQ¨>ˆY†Zߊ €ŠˆXd©X\d™iš¨¡*\EJssg ¶rn<¨ i¶CEJsÕp€›AG^U-ð"æ…L½THm‡%Û¸ÈÉ*^„¢À¥ �’•p׬–j]ŠÛLiz}ñF¤½~QE=xÀ¼Ÿ è‘Þ™^„µZ@7ïsa¤$#W*´Æ‰EXV=ìo6‹ðdÚŽ\ª‘ï§L™'EZEÅ ¯ZõÁ£¨Ô»i©EË”½$ÓAq#µ¶ºÁ@Q{S²°—v\nE32L¸%I~ú©Aôz`KA(˜C‡,¢ª.Ú«NR`©ÒŸÊ¶å ¡Þ ¸C’eE‰Æ‘…!|y&„”ÓJ/TSÑ 2N¡2à %D‘3Æø"d7D"IS%ODSÑE¸W,cvh$•Eäñõ@>Ûw�“ˆM×P‘×L NJ†7ªUpÉ)Ã{8„ÄmNPîÓ¶´è‰ZË›„Í@Ñj¿I1ƒm%�öˆ”ÁAR©\U«Æ ÷Ó#nÖ뤫�§?r`ýèo#¤„†Ú�rD¢ÕSð±0n±DtBÄBNMp_N0ó`•"mQ:¡Â æ�¡*¥ÂT§?6“.·ºkt@ ªŠ©Šb°ëmÑIEiùV‰§Y]à‰RµNl8åÓí¶C¿Þ¡*®JY¬+&ã$ii8d4¥Æw.O0å,T¨-)ÏÍ„+΂²Ý¶ƒj¸úÖ�ÄÀ3¾Fî¨"¥qçJþ1‰wP�–ŽªŠCy`©Æ”Æ&d_ʳ’q×)x�Ú^œê�ÛÊϾ D¯²•UªÒ¼ßr@¸Œ-Ø@œ¤¹|eÆ—¶a8j”^Pؽµ§D#fÙ̓ÊJŠ¢QsXœSgÞÃ06 ×ãÊà ýÐèÎ #³Þ¢/Gòƒßª ÑsDQ¥¿Ë¶, — ˆÆÄôç^ÈQ̰^4¬,¿½\x~ ›QË‚ÕR&™AEt‘QkjS(aÙdBVÕT…V—'}êl6ªŠá8£—¢Áµïgjå TçÆ‹U„uá¶®"£wVÁOl$²Ê8ú6”lÛQÅ9«ULaç ßDhò .©J z#s†þ—ÿ™[¿œ&õ(á*™§\~hÒH¥ü%iѸVŒžvŨ¡¸ª‰ÿªq lÅå§T É¥jŸïgˆ’Qøg qXÐ=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê�Õª4Th¨Ð=Q z£@õF�ê‚N þ(zýžå‚e�TT 9—Ýï..4ªSï…¨S�ºœ•%U¢"B6`«•i�ûêè•ùÜ&ÁZJt­#Â?€~è%™Zš:(‹í÷O×ìÿ=~ÏqÄ\ÊŠž¨EËU9ªI� P¨´çHDFÛn¢µ@\û ?]"ÓÄn\>¨ÃæƒÊ[WÍõFùA«óÞ_ʯWd5j¢-üé^e‹œrîaAäç„+ti›ƒ!çÅ3�Õ�¶ENJh(S³wf±äÑýFáu9Ëûÿ˜mÐï‹J9,K¡%o­|eüVòKpáŸÎæÝkQkL°XDCÁE0æ\àÎÚTÿ�¢{§ëöŠ¿g¹C/\Z (™å¢/4rGÔ‘ŠEh•…J&9Åû°»�#Õ’"¢ó,w ýØQÝ<"Ôlm^jEiŒ^�…Üi � EP}IóÌý~ÏñQSš4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »#AvF‚ì�Ù ²4dh.ÈÐ]‘ »!UyÿîÒ ÃȼԬxOðÝü÷G„ÿ}Ñá?À_txOðÝü÷G„ÿ}Ñá?À_txOðÝü÷G„ÿ}Ñá?À_txOðÝü÷BŒ»ÈdœÔ§ýƒêÿJŽÇÖþ•ÿ°oý_éOp­2^ /+WEѹ¬sHø&îD+WšC"gåV]X]é%Q YYu¼’©Šc 6ë*(k�S¦&.må!4FÕ)EEXÞ¹.¨>´X¼¥–ž´†E¶È÷‚œ5s¤¾P»,kî1õ¿¥ìÿWúSÜ~dìqÃø1nìiϯ–îYeÍFÅ?Rlæ˜7 á[@ë…ci˜L²[ô ,s®%€�jõ— åµÉxC-’�ª\¶¶Xa¤7E'D¨«ÍÆ&LßdЦAİë…bbeͦ(ÛņèꥌN ¹&›ÖÖÒßT‹× <³ P¥­zKÓ—øªòëZÓÜcëJÿØ7þ¯ô§øì}oé_û¿•¢]\šÖ<7쿼xoÙxðß²þñá¿eýãÃ~Ëûdžý—÷� û/ïö_Þ<7쿼xoÙxðß²þñá¿eýãÃ~Ëûdžý—÷�ù<®’iäÒŸüCÃá·(§E,=°ˆ“ÈòQTÑmä§!Æ]hWJ’g÷tÂ8€í¤œ�·üz`…[6ÈsþÐᎠ‚ª‘Þ�PºÝæ®^¸UÝ:­¢Ñ\¦|)n�Ý"Ñ]§'ïƒ%mÒ2Bq¢Ár«B‚Þ/$ê­§ ->èxÞ,árþP€ho=Î ˆ”„@iÓ5­A§ÅmÒ;”T)EJgŸ®E§I¯"˜à´_D �:FµäS¦u懗´í¢¥0Hp\y´Q*QI Ì�¹´*"WÍö¬/ÅžG0ä­¸¥}pŠR¯b¨ž.kÓ5© ¨ªñùöÙ jv�h”öÂ"K«J•å�8z7^ö&ùÉT“²w(è â=<Þ˜r¨èµ…¨á\¾˜q…d•IUꔢÆ2ï¨âª½ÉÀ«‚V�—U$Á©m8ñ…•÷º×B9T¶œxÃŒûÚåR+ -¢ªÒ°B¼p�{ãd­¯«/ï  ÿW?e:`�VÞ1,> ÛiÚ‘ToQU['*tOL8N ¸7® 9J.”¬-ínõxÕÌ«â2® ùª•Hx¦[½Ë…Ê--DÆ�åÉ™ÖÐy7×´>å‹€n åÇ_do�”{“ƒiPÃÓžpGÌFªŸö,ýÚ:Ø}$¬  Šd‰þ(ŸýÑ\¨«�5X°ÁÆèPTÅqDJReÐ>`ZT¹°¤Q%�¾áE·%_\nÐuŪڈ™Vž¨ø KLtãE�É:Ž%—eŠ~=�á­„ ƒŠ¦8ÃŽªºˆÚTÅ´9éŽkÕË�‘ÌcÉm8SrÍ"¨ËÖ]b’¦YS®.m§Im 5/á!i.íQhIÉäöÂLQ@+Êá®4ë@Y¦ ÏÑÓ®2ób©ÉRLþî˜ KÌ|'{ä§/îé¤VÒ­ 4ÿ° m�ê[xó{a܃i‚o(DµN¤’Ž¢iuÛ”±L±§4­½b((�š*­cüÖˆiZÿR¬:M7R,²ó– …möïÖá(ª”Ï6­Û�ú}»TUK�¹"*„½$˜z;` ÚZW/¥U!B˜ï+üu†AÅNX¶v­aÆÖMÍÇy|"*ªQÕªú ›ÝîïNU0^ˆÝ$¾ê¨·ª*eÍHÝ{Ø›ç%RNÈei§>¨uU5S©?ö-&ÑÕmÍnõþÉ}öt ¢¡`¹â�M˸Ù*  ó Öìz’�óuS1:r ë2m ó"œ\SѾÍD Z õejñ›'¢äLSŽ J´ERèHÌ-Ê5¢µjÕÎ|H®/·Û¸NØ—Ôy9 ©·'èéf±½GÍÄbh˜§BgûíÔZ¯Š4ÏÕšQ*й‘G4ì¬!©#-•KÄS›§žrà¾K[S¾%^0Ê´|‡9ú#v.Ü!‹•¤õÃn¸àÔ‰[·�7® ×&ij»°Tχ¥U}hë͉!d¤˜B�¿Q¶ùTQQÇO\„Ǿ!Å3æÊ³sá�m"¦~˜yÛÐÙi1J\_w4TfÕEWíL³=°Mº¿Øâ¾w‹·»¡AJru-3õ@ïU·-§�Šö§ò„NÄ%5§Ñ**Â+{Gxu䂨·ú0HAiß„qy n^ŸT«`žJ%I`YqËÆµCTƘçã%lºxß)ý¿»EsiQyÉQS²DñŽ‹Ô¿;^!—'Õº ©ZÒ”ÇÕ®¥É\=d´í…u¤…ÚóêªvÇ&FŽªkq“ÛÍ×*¥|b_l+-K/½�n4 uR¼ÿŒà e\KJ¨âžŒc�Õˆ"´Þs¬{؛ƪ¤©‡ª�)ÈpPÇ$OÅ!ñ¦~îöÀTS¹yâÆå‰$åÝJ§«±%RªÑn8ÊÓ+ÐË…nªÂ(¶®¶©ED¥RÝÎ輚§£à¥ú¥ýજ¢%%ëû¡˜•x�æÄmoÕ�l.J+¥’FÕôÁ!â·gÆ Ä`œ®š}+耽§l9Z‘W¡}pDÝĆÜUÅ2�{PW‘NVJ¾˜@÷±7LUIS‡4#Œ¢oÀpú\F·¥Ý6DQ-øöC…m…ÉP¯2¤UCáMHÍ+•ERŸÉ!ZI3E,ˆ”h>œ ÅÊÄöû èÑ8Ù­ÜšTVßf�rQ²áÅ`·*H`â ÑE|äüv÷o’6¢˜ *sC70MÚ·ª§OoþÅE32v4Š•�û#û£Ã¾Èþèðï²?ºfXïhô•)Íßüo̵xW<²ÿp¥¦f¬t5ì—ÙÒßüSϵxÓ,óŽéoþ)çÚ¼i–yÀËKM^ééÙ'² ZfjÇCPîÉ}�2ÒÓWºzGvIì‚–™š±ÐÔ;²_dxwÙÝ3,w´zJ”‚™™; EJÇt·ÿóí^4Ë<ãþÈþèðï²?º<;ì�î�™–;Ú=%J|û›úŸÖ�ÖÒÚLq¼î?=Q0NˆÿZÚ[5ŽÇç¢. Ó3,v:J•ò°32Ç~Ð=%AO«(înÿôƒÌµ8×= ¦fNƃQQ¥�™–;ö�é* yN¬ ¦feotõñ´öÀËmÀ± ï£ZùL4ô@ËKÿ“'¤kOüõþ)í=šÆâQšXw!R¨ˆ¸.9ªÇt÷ý÷�p i†]Ý=ÆãsãÜJá—L5´ö“ù·«yÜ£Z*¢`˜d‰33+cA¨·Ž/¶Ù›5ÿÍXØýUÅyZ«°æNýžzš¥<�ÙçœLKK�¶�kåÚ[IŽ7�Ççª& ÑëC»Kf±ÂøüôEÁzbSëÿZüû›úŸÖ‘øùosñòÐ2ÒÁ{§¤kO+ó32¶4\[Á_}113,v:ÚT¯“Hkº_²ž­ú´­2åjD��üŸgÔTª~þµ‚™Øg~Ï>ôTm=z±Î±úKÿ òú>®ŽVª@ËKMY±Hîëâú®Õ-35{G¨wn'²?F¿á^CGÖ×ÊÕXwº^ …ýëŠS,s¤KÌÌ��×+åîoÃì§©f�­)\ùZ‘a­§´˜ÜJ3[Îä*UÇ5Hwil×øXvýEÁz`¦fN÷OQRžV?-î~>Z%>¿õ¯Ï¹¿©ýi lݤÿÂÓóÕSè�õ¡Ý›³_á`Z~z*â½1/32v4\T¯”Xwil×øXvý4EÁz`væÜ öy÷×kO¢˜9Ò ga�›;ÑPW׫•ª°3;pïØ‡ßJ‚ž­<­T�™–;?&CIR¿ùëüR&&eŽöŽÛJ”ò‰ m-¤ÇÎãóÕè‡v–Íc…‡qùè‹‚ôļÌÉÙ±늂¼~¶¨˜™–;ö!ÛiPS‡ÖÕ wKÁ1¿?•ZeŽt‚™ØgcAÞŠ•ò˜êé�™Û‡{GßJ”ò˜ièŽén>)çÜïežpïs| 3ùT®xçXkfí'øÞŸžª˜§D­ìÝšÿ ÓóÑWé‰O¯ýkóíÞéx&çÅ)–9ÒfZVö�IoOlxÚŸß33+cA¨·Ž/¶îo‚af*•Ïë lݤÿÂÓóÕSè‡vnÍóV¾¥\W•ª°2ÒÁ{§¤kO+ lݤÇÂ寧˜§D³_aËý}]¹ÄÆÃ–; mj„¾2pÖÍÚOñ¼-?=U1Nˆwfì×øXŸžŠ¸¯LKì9“½Óºæ¨Iã)gìÝšÇ 力¸¯L ¶Ü ú5¯”ÃODÖ;Ú=-QÄúYÄÄ´°Y±ÛF¢¼>¶¨‡,w´zZ §ÒÎaíñÐï­P—ƹ1ˆ™i[Ú=%¼q=°RÓ!c¡¨jêÀËKîž‘«©-,4Fµù÷1-,ºvÚ5§Œ�×t¾e3[ô+ZeÊÔ©3°Î÷O½)ãc«¦?IÏýþNŽˆ-‡°Î÷O½5BOåĺa­›´˜ãx]ôSè�ûSûà¶Ã;ôÕ|[—é‚–™š±ÐÔ;¶×Ù·%‚ý¶z]­úT§‹†žˆ™i[Ú=%¼q=±13-+c¡m¥¼%ñ“Óó3'fÄ ®* ñúÚ ¶æÃ Ú>ôíE<[WéŽén>7çÜ× e–P×t¼/òâ´Ë ©óîbfXìt-´©_ e¥¦¯tôŽí´öGt·¤}ÉÆ™hѤ¿çþç'GD Ì´­ûô–òž/®íQòŒúÊ•k¯5‰‰™c¿h¶•ND¼ÌÉØÐ]qR¾*Ã½Íø}ªõ,Ö5¥+Ÿ'J,ÃÛ‡c¡ßZ¡/�rb=ÿJúþïj‡{›à˜YŸ®xçXÿªý?÷tļÌÉÙ±늂¼~¶¨wil×øXvý4EÁzcååý�֋ךC[7i1ù«Âàôªbœ­T‚™™•± Ô[ÇÛ-°æ¬t;ÐîÉ|lu'®[nM^é÷ÑÝ’xØiOTþSñþg|üRîo‚cf|V¹ã�aÝ›³_á`Zb*â½0×t¼/òâ´Ë ©óîoêZD§×þ…‡{›áxY—®xeXý%ÿ?÷9::#¹Ÿ“Oüoȫƥ‰á•cº”¬|oË�ËÆƒ€a•#ýhw¹¾ …™ðJçŽu‰M§¸ø§/—rpTË<áÞéx&çòIL±Î�ïs| 3ùT®xçX—–™ Ú;®ÓÅX)i�³g†¡©/“ëÎîo‚af*•Ïë-,ºzF´ò±/°æNƂ뚠¯Š¥œw7ñO2Ôù*çžq7õ?­ fe¥ohô–ðSÛ-2:†µ‰}‡2wìóºæ©N%žyû3f¿¸”f–¨Tª"®+Žj±/·&BöŽë�¨§Š£—Ïɉi`½Ó¶Ñ­ô5q=z°Ê±ÜÝÇé™rñ®z4D¾Ã™; ®j‚¾*–q1-,í³¶Ñ©'«¦ aÌ�›@55JùK³Ë(¹,mÒíkå-Ë, væÜ Ý>úíIÊfdìh5+í/ÛòÿSOfPïsg¾)…Ÿœ¹¥s¬ ¶Üš½Ó$ñ°Òž¨wfì×øXŸžŠ¸¯LÕ~¿Ÿûºb^fdìh.¸©_`feŽö�IRžJ nL…ކ§jKôr‡v–Í…‡oÐD\¦nmÀ¿gŸ}v´ú)€ã�#¹»ÿÑÿ2Õá\õëŽæïþ)æZïçžpîÒÙ¬~jÂûԋ‚òµVaËí–¨)ô³†¶nÒcóV7…ÁéTÅ9Z©ìרrÿ_Wnqà?j|wOq¸Üø÷R¸eÓ-35~Ä=C»§‹ê»T¶Ãš±ÐïC»%ñ±Ôž¸ùyG¿d5¢õæ‘ÜÝÇÆüË“åkžYD¼´ÈXè]pÖ¾2üû˜–– Ý;mÓÆH‡·ÇC¾µB_äÄz!Ý¥³XüÕ…‡w©åj¬5´¶“o;�ÏTL¢Ú[5ŽÇç¢. Ó3-+~Ð=%¼§”õÓ(înãã~eÉÂ¹å” Ì´­Ž†’Þ6¾Ø‡·ö�¾µAOäÄz ¦felØ�¨·•ñ}wj‡vnÍóV¾¥\W•ª°ÖÒÚLq¼î?=Q0NˆwilÖ8Xwžˆ¸/LKËL…ûî¸j)Çëj‡vžÍcq(Í,;�©TD\Õ`feŽö�IQ¤‚™™;6€j* ø½YG‡}�}ÐRÓ3W´z‡v)솶–Òc�çqùꉂtC»Kf±ÂøüôEÁz`feŽÍžJ�¯�לKËLÍ^ÑÝpîÅ_ò}ïPÖ‰ûúÒ&%¥‚͈Ú5áõµ@ÌËJß´Io)å=tÊî—Àl¦k~‚¥kL¹Z•#ååõ�h½y¤;³vk, ¾‚*â½02Ûp,h;èÖ¾.z ¦vÙ±½}z¹Z«Ìßïö«Þ%¶Ö‡\ôéH—–™  ®ׯ_Ÿ…32vl@ÔTñ{TLmÉ`½Ó¶×jIã å wKþ_Æü¸­2åë¤5ÜßÆÌø­sÇ:ýÍð¼,ËŠW<2¬´È_´PÔSÅêÊZX,Ú¤j+å:²†¶nÒ�áhyŠ©ŠtDÞÌßüo‘ȵx¢ç–Q)õÿ­aÝ›³_üë…�o©Wäé¬;³6“ü/ CЩŠt@ì9c½£ÒÕ>–p;XìÛa¥ªWÆ»=:`e¶à_³Ï¾�[OVœs¤Ö; -P—égXïÚm­Rœ <²†»¥ðšßãR¦´ËÖ�32Ç~Ð=%GÅêÊÚ{I�ÍX^w}DÁ9Z©-°Âýˆ}èj)ëÕÊÕXkº_µY­úÊ•­2äéT�™–;Ú=%J|û)™“± ÔT¬LLËûí´¨)Ãëj‰yi�½£ºá­<¢Ál9“± ÔÕ_¥œ~�‘ûü­}1ÿUúžîé‚Û“!fÛ NÖ¾5¹iÓ--5{§¤wm§² nL…›l5;ZøÖå§L|¼ÛÞ¡­«$��›gÔT«½Y, ̱ߴIPSÅêÊ?IÏýÎNŽˆk¹¾ �™ü’׊`8çH)m†´}èkO]032Ò¶:KxÚûcô—üÿÜäèè�ѯøW�ÑõµòµVÚ{IŽ�ÁèDÁ:#¹»ÿ�ù–¯Ê×<²‰M§¸øß/—rñTË,£ñò1ÜÝÿÅ<ËS䫞yûKf±Âø<ÄEÁza­¥´˜ãyÜb¢`�îÒÙ¬p°î1p^˜�¸v:ô©_'†žˆ˜Û’Á{§m®Ô“ÆAÊ%å¦BöŽë†´ò‹-,4Fµ‰½™¿øß#‘jñEÏ, feŽÇCIR°2ÒÓWºzGvÚ{!®éx^7åÅi–SçÙLÌ��¢¥`¦v߳ϽO^¬s¬;³6“ü/ CЩŠtC]ÒðLoÏåV™c�#º“L|SÈÉÂ…�ã�cý)™™[öÙê-åô[Êzõ-s¬KËL…Ž…× kä×çã½ÒðL/ÏŠS,s¤ ´´Õ�‘ݸ¾È)™“½ÓÔTu#¹»ÿŠy–»Æ¹çœ~�‘ûü­}0×t¼ûצXçHwfì×ÿ:á`[êUÅy:k33+{§¨·�§¶üšÚ^ �ìaÁM9IŽt\ã¹›ýÆÊgĶêT+ž­K2Ç{Gm¥Jx© m-¤ÇÎãóÕè‡v–Íc…‡qùè‹‚ôľÙ;Ý;®j„ž2–q1-,l@¶Ñ¨¯­ªîo‚cgzâµÏë-·ý¶}ôjIêÓÉÓHù F}eJµ×šÄÆÜ– Ý;mv¤ž2PÖÒÚLq¼î?=Q0NˆwilÖ8Xwžˆ¸/L ̱Øèi*WÊÀÌËÿ”Ǥ©Oü4~+ wKÂñ¿.+L°ÊŸ>æþ§õ¤5´¶“o;�ÏTL¢?Ö‡v–Íc…‡qùè‹‚ôħ×þµ‚™™;6 j* ø¿½ªü¥Ù¾ …�áÁy+ŽuL e¥¦¯Ûg¤wtñ½Vé‚™™;6€j* ø½YC[Oi1¿”z·�È5¢*& Žh‘13-+c¡m¥¼%ñ“Ó lݤÿÂÓóÕSè‡vnÍ…�iù諊ôÃ[Of±ÆÃ¸=(¸/L ¶Ü Ý>ú5§�†žˆwfl×÷lÒÀµJ•TUÅpÉV%öäÈ^ÑÝsµñTr‰‰i`½Ó¶Ñ­<šAl9“³h¦©_)vye lݤÿÂÓóÕSè‡vnÍ…�iù諊ôÀËKû<ô�[O¯8i`³ò˜4�kÿ†�Åa®éx^7åÅi–SçÜßÔþ´�ÇË{Ÿ�–‰O¯ýkSúÒZX/tô�iåb^Zd,ÛauÃR^?WL;´ö“/;�Љ‚tAKl9«ô;²_I놻¥ðû)êß kJÓ.V¤H)™™[ E¼q}±þ´5³6kül OÒ«ŠôÇswÿó-O’®yç2Ò·í¶ÒÞS‡¦™GÈJ3û"¥W¯5‰y™“½Óºâ¥<šÃ»Oi1Âó¸ý˜'DwKqñO>çxÓ,óŽén>)çÜïežp32ÇfÏ %B_¯8wò—fø^?�ä®U2‰y™“½Óºâ¥FŠÇé/ùÿ¿ÉÑÑ3°åotûÑo \l·Î,ŠžuùÈ{h%ýç%t@üŸœÂÀ ©5úÎ3Ay&H©6 Éxóšæ<1å`ÁA͉öÆÀ5¶„ŒeðY:žAÏ ¤ÁüŽ5ŽõÆÒ´¬>ÉÖ[Ë–È\Àú›(„ï "†¯@ÆÀ7Œ„Œeà²u<ƒž*pß§‡\8’½LV8²…¿L#C]á�›†x<ÎXäò…¿¬HüK?JŸvh=LdÌK`8/ À&“¿¼ ôÆtÝ™ý¨Ôb 41þ±C"ylϰ2-òˆrë�ô°‡õ�ˆlóWšñÈe‘'Y –‰õÉ$ˆI¾ŒàŒŸL^B’/¼7�ÚéT$3“ëqbÌå‚ï¨ó1¶¸ûÆNSP0†e“ýç´À9ÐIá‚ ‚ó:~ñµyàò}#Ex›Ý~�Pr„ç¶aäü8FÒ$ŸôóÚj"BóCîà$‘8ÿ?üVR#p¿µNˆ=·ÓCÛ#Ó2¤OlëÛ†S VúS;übeE¥ÐýÌb\ÈP‘½}$M3oS�ïí¹ÉáвBƒz§¶€3TŠ™Óφ$mu�œ¦y·„‘Ì”—·\4âj�{¬U n8�½ý±ÃL’àYßö²ó;៊Éÿ«9Ö\* G¢E÷ÉOBø¦ë½eÅŠ¨ftäAòp ªì�O|nbRQ^Ç�²[7-„÷9ö¡µæ}Ÿ·óŸÅe7€ñ*Üü0E,HñMùNI‰4ùHã€øøü?ÝF+zYr3Õ…­ñƒ¿¦� D¡þøÂ…q˜áàï‡ç[Ì7eö„{b4=ÿO§ 71»žºÍ�“ ¸.8P}¿ÜCBtûÊœU0޷É|X'�•ôÆ y�írí,#Æøû~2:1 ééãë�¿GRÌùHabƒ’Ü?ÝcΓg Séý?ž¨ŠÓNê�¢ &@§Ô×,I¯¦€c™ÿ ñ�øhÔÿÜÝ‚nL8N}àû ß_¡TãõÿtÔ*C§mÐW ÂÚô ûáþ‚\Oø�Zž¼~°´8ט$éßê9pQ©­}œPsðRL” þ˜è¡ÃnÉz1>ÿY(ýgý#žž�ÄÏêÿâä]¹É868ZuQEPéÂgãøYëKŠz üâ‰.Â}Q±è9«­&¸¢x¦e¼ÛE¼ç¤ *Æ¡Š¬ï„F‡­™©\8¶ë“Æ‹ÅPc—ºV0 8­ÏÛ‰Ôb@%*bQÀÀ²*…€ž™jÀ�b�qI± �vg iX<DäÄp’ÇMŠ5ÂpË#n–Œ–8³›³(Yp½`2Æ®½%±êŸ† ÃOÞL5U„FNÄT€=R& £[ÊX3 .i(µyäR…Ö¶÷1Ôw•±‡Š­˜x‰¾@�ɱ:J““·ì! A2G/¤Á€Ð‘¢µíˆöae'Ø'í… ªTRNÑÇ[o|œ‚QqxKÚ�\‰‹!<@ž™Íó‰�0ƒ+@v‰&ŒHìR•�Å,ð‹ärl I\÷ŽFh~Ø=ÄÞ± ™@Ú&㵊ç9…dõÉg¾?÷ÅÃ$<15o®CçWº¡?aÈ—·¤;�N¸ÕܾT ˆ X®x¥?j � &¤á€p”I’TãIŒ‹Ø•$Åß(Á˜Œ€&ÒiËX23[®90!˜“_fý²,s4˜ ë€lS¦ØnÆ×K]2ZòÆN¡G|¼8©•Õh B@œào¡iÉõãÂ0 â¦OäÓÊÚÀ—Ó€Ò¢¡V¡L1·XˆÄR¾@ ©e^X-˜‰AS'ì1z2€)Ä„OßEb¸ âa'yx*J–¶Lt/éÀÄÖ@Sx¯¢‹¶mj®�;U© ©�Æfr6Ê•¡‹�®¸*�cj¯P%ðãñ¬ýO|žf€7i"¹QÖ0Ö*P¨Œ"Pã9 ÒUË9'Aox×JP’e,»|€@-l¼²Å1Ç÷a¾x:½¤uc„æÔÐÄŽ�â�Îi[¦•RЮFíÎ_ØtÂHp ‚T¤ÍíÈØ¬A'$)5:dœW(@ž€Í«z~†1}ØÀqd~¢CîôÀ\£ñ*ÏÞHK8âÑ@MrƒÛ"×� q×ß!Ÿ(ˆE¸<º†H@Q˜’V#”NHèŸðj"Ò°“Qš29@;á%¸é¸Ê•R#Ð@ø~±Ð9³Å:z¹÷šâ=3µF)—"zL{-$8=À xõ�²cÑ\%Äœ[z³ÿÊœ’§LdYB�sx!»Oü�É3˜‘§4üfÝàÀýœñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒñŒ«Á€ûö÷=_HFŽjáÔ±ÙÌ�ýchP+ï…2eéúÃáègKÅ9d½$»ì_ùÚhKÞ~?—Aê5 ÇÓé�¿Xšú»ž�û{ž¯£*åÕ‚?ï$‚`%ƒ}aûdN¬ÁHî=RzãßV‘qß®k�5OO3 ‡‡LBìPž½õö`G©˜•fÁ(¸Ï2ùáÐâ]JF…6{¹14SL3º#íËx0¶)5ÄpÀ�+ŸØýÌQ)6/ºøÈf ¿IΟýQ#§À.sK0Fö”עř ãð_^ç£þÞç«è0pÉ Œ½C‹žrœlpvd•î蜀�†IÏÉȾèňÞu{5ÌóÅAT�1~ËŽCZ7�ÍIa‡,@�‹1YO„ÎÒ¡‘£zÌûž�ûJì*c<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë<·Ë h*cÿZ:¡$Š=�ûÜqÇqÇqÇpꄲ(÷?ú.‚Ké©pw‘ZD±;£ƒ–ÐE êôÊ[pPÏÙå•Å‚Œœæc•&$ Ã1•ëô´îSX8‚„È nXá‹ÅâQóp85çÌ™1Þs9#ÌÑÏ(Á²÷?ðrR*“Û˜|a(*ò§„Ç¥åeK ýäˆFR`wÃ,Üv„…àsvuÝž'jëqÛ$I‡.¹ß!ßÙ‚«©öôo¥à#D;Å|#}/Wµ Jònà¬õ8¹�7ñ"Ë}„냃�˜Šû¿³’=:…Œ@Œ½uìh“’‹vMIÅ�j…ä#”�eY†>�¹^7ÔxF ¥"ti[ë¯c,¶Á'ºk^»Ä`Ü= £!95Ùz`6Ë¢ ­|å‹à¸]"ß;]W"º„ÀûÄûÿàÀ*Rõú“*d¾ �ÀŸô0�þÑI–�åZ �%ɨ؈ʥ>¸0)X›Cu7YSy)Lׂo *–È•Ó[¼R¸CŽÍ¸†ºnòXƒ¸/‹Š†ãÕ¾¸Žaébº_Öjà­1S"l]dÉ‹‚–E*^1y5%%XLÕ$®«°LĦ¥l§ºå@ÜÁ¤Òâ‘cY�1}2­"œ@<ÓAh`�C* ºŒûÇ> Èè’ Ã…Wþ͵àL*§V[²mU{Üdq†ºÑ» ÄÅÆ·ƒW¹¶–Ep�C‡÷è³�ë&&FC‹ hglýmÓnʈˆ¢17ÒˆO%u`4Ó;@¢XTÄŒKh»6^úä£9ëdž:ŸO2I½ÜÆS²¹ÀÒØ«„HŸÞ+d1ÀI-ÆÂºÖX8ÁI Ë,n*}2¤½ŽQê_ÓÿálF�#*“,ö³dePÉH•ÜtÀñÆné"”}ž˜ƒè�ÇA.~A\�âpDD³V´c oé¡’Âo‡@xÆNötÒb!‘ÈU®=U5‰`\,°>‰Ý…O‡ 3ë/®D��¥Í¤ñÂ÷ÿ¨t‚ùpŽ$4Ê¢ÐqG¼õΗ %�NÃß– $1âÅju®¸4YQÓ uj”Џ– ’ÃŒ´£Ä§òèzàç>hJ î½0цˆT#NÀÄÄpk†wƒ>ÌðkŽ{¶‡Ý ¡G ®þÐ%‰m3µ_!æ-üeBå)@5åŽÿÆ¢�·µÏ-Þ'ÑGÑòÝâ}àÓ÷Ô% m­rÏ褊%´Îý�i­.vµ™QÈrå�Õ}õýœ²«³p€+ËíúÎÿ°àûÔåçåˆK[x·ÆWl‰d¶ßö_jŒìíþHÎ0ý�(½¦vµ™QÌråží¡÷J¨QÇ;]å¢Ì¨þ¡(!Eh?𦟠Þ'ÕOÑðÝâ}Vç~ˆH^Ó;]å¢Ì¸Ì­(OÅŒ¨ìÔ¥ óû~²ã³r…+ËéúË�Ê‘yî8ÃôJ¦ö9å»Äú(Ï-Þ'Ñfr�È¥óÆs�È¡ <·Æv·ÿeCuÈH+}»eÃuÉB+}šgvn»¡¬íoþˆÏ Þ'ÕOÑðÝâ}Vÿ<§·¿ù#/¿¶Q¦[¦µßÚ Q-¦v·ÿDg†ïê§;{ôžb߯\P”5¶´9Úï)}Tçµþ3”oU-½i]ç†ïê§<7xŸU¹Î7ª‚Ö´óµÞRú­Ë†ë�”ÞûtË­í¢Þö.ó”~E*mç¾2ã{j…w½‹¼¸ÎÖ¥ðý*œ¾þØ!F™lr«ûB�K+I—_Û ÛZ¸þ± U¶ö¿ÎøGôJ¶´9ÚÖe×!Ï–TwT”µÑ¶vûŸ~9Q�½Aáú¹Úï)}Tý5¨ÎÖ¤ñýªÜ®þÐ$Èi2ãr”¡»òÓ_œ¸ìÜ [òú~ò³òÄ$…Yã;{öžBÏÆ\oiR®÷µ5œëòG,ñ�ùÝÚ3¿‡öèk;øN¦òãóB–4óß9QýBPEh3³·û#*?,J,å˜áÑ BÊÒ<íïþÈʇ͊YË<ç(ÞªK[Ú;ÎÝyCê§*»5 B¼þß¼¸ü±(Q§–âó²ä%[ñ½?yQÙ¹@åöýçoÿòGï8ÃôB@¢ö™ßí§\¬ü±()gxÎÞý'˜·ñœaú%Y{ ¸þ± U¶ö¹Ûßü‘•š”¡^oÖ\w\”2·Ù¦_l£L¶9Ë?¢’i†—9GäR‚Žyã*3·¨<`³;³uÝ goòGó¾ý’i­._l+l49ß¿®èkó�¾Ç§ßŽ^~X”1§ƒ|gp_yfs¯È! <·Îq�è„‘Eí3µ¬ÊŽc—<¸ÎÖ¥ðý*œîö;k;{ÿ²3»Øí¼å‘J 9çŒíw”>‹3²¶?£;}ò^bŸÆWh�D¶™YÝ©Io�²ó»r†Þ-}#·¿û#<7xŸU¹Ûßü‘üöš{{ÿ²3·ØôûñÎú~£™·Ó†wÓ÷�¼~�oöFwðþÝ gkðFv·ÿDgþ‰C^Ã*4) g<ñ�­ÿÑqýBPÖÚÐç(ÞªK[Ú;ÎôýSJo?¶(Q¦[¨þ¡ ²´™Â7*P yc¿Æ{6‡Ý*±oçÕAk{_Ï8GôJ¶´9¢…•¤úvëÊ_Eq¹jÝù靯V~X”³ƒ‹3œ~E ,åž3Ý´>éU 8å×öÅ 4ÞÇ*?4)!G<óôÖ³ûb„K%°Ï-Þ'ÑFynñ>‹2ëó@–4óßÏ?¢†,–ÃùåGõˆJ´ÎÈ>ØþŒãÑ FÛÚç†ïê§<7xŸU¹Ë?¢–,†“.7¶¨zКÊÏí A,†“*ªJZèÛ;u媜ïOÕt5ž¼OªœðÝâ}Vçn¼¡õ[�­ÿÁuù Kyþ¡(¢´yý±B�2ØåcåˆH 8³Îxnñ>ªsÃw‰õ[—_›5·–øÏvÐû¥T(ã—Ö% 4ÞÇùÝGõ‰B%—°ÊÎíJIY¶í�ªù1Oç<·xŸEyùbPÆž ñ�ϵÚ3¼ôg‹|sÝ´>é 8e÷ö�"4ÃK�ºò—ÑF]~lÖÞ[ã;Ÿk´åcåˆH 8³Îp�Ê„ƒ^xkñ�Õ}±ý³”~E*mç¾2ñòÄ¡ <ç;Z̺ä9òÎè¾ØþŒíw”¾«rãºä%·¾Ý2£³R”+ÏíúÎà¶Ð7Þp�脱ei?žT~hRBŽyã9ÆõPÛÖ„Övÿÿ$~³·¿û#;{ÿ²2£ób‚–rÏ9pù¡J[yïœðÝâ}TçéÔßÒžÞý'˜·ó�ºò‡ÕN\omP®÷±w—”¡-ßž»üå×fáß—Óõ—ÚT‹½i]çÜ©B·å®ÿ9ØÍ�ç¡Ï.¿6cO-ó�ªù/!Gã*;7(Fü¾ß¬ík2ë�çÏ.?¬Ji½�óº�ë” /iœãò(CO-ñœaú%Y{ ªÞÒ% ohï;}�_· îö;o*7)BI¯<5ùËïí‚m†‡*7)BI¯<5ùÎè¾ØþŒî ï/ìË�ÍŠÓË|ço±é÷ã�­ÿÑÛÿý‘ûú5–!(sÌp‡è”Eh3·^Pú©Ë­í"UÞö¦²£ò„ ŽYŠŽÍJP¯?·ë*7¶¨C[ØþyMGõ‰B%—°ÎQù©·žøÎÎßìŒïû½ÎVvT„ƒ~7·ë9çôPÅ’Øgj¾CÌSøÎú~㑳ӆwÓõÍ>¼rë{H•w½©¬ömºAbÞqù¡K[yîÜ¡•›f™PÝR×NÙÝìvÞwf麛Ê�ÍŠ YË<çt_}fTomP†·°5—Ô% m­\7\„¦÷Û¦TgoPx~…nwf뺛Îý–,­'óÎuù ·–øÎÈ>úþ̸̭(OÅŒ¨î¹(-ï³l¾þÐ$F˜is·ØôûñÎÿ°âûTgn¼¥ôQ�éú.¦ó¿‡öèké÷§èº›Ï-Þ'Ñfynñ>Š3Ëw‰ôY— Õ!(­téœãz¨mëBk8ÃôJ²öqýb«mís¿‡ôêo.?¨BDi­._l+l49Ûßü‘üî£úÄ%‹ÚeWfáW—Ûõ�ºò‡ÕNv·ÿg}?qÈÙéÃèÖ~P”£ƒ<ç³h}Ò«ñÊ�Ê€ 9f/?,JÓÁ¾3Ù´>éU‹xçk¼¡ôQ�¾ù!gç;]墌íw”>‹2áób„4òß9Ûÿü‘ûË�íªÞö.ó¼ôg‹|~šÝvn¥y}?yYÙRPx^߬áÑ BÊÒ<íïþHËïíUm–×*¿¶(¢´Ý›¦êo;}�_· ìíþHÎÞý'˜·ó•ßÛQ vûëì‡GLî m³|ygþ‰A4ÞÇ<·xŸEå»Äú,Îq½Tµ wœ£ò)So=ñ��¿ÙqÙ¹@·åôýçp_yfs�ê¡·­ ¬òÝâ}g–ïè³.?¨BXÓZ\¸ü±(Q§–ã·¿ù#ùå>[¼O¢�£å»Äú,úSQù¡I 9çŒí÷×Ý®¹yùBPÖÞ ó•š�£žyÏvÐû¤(ᜳú)&˜isÃw‰õSž¼OªÜí×”¾‹2㺤¡·®�3Ù´>éU‹xç8Þª [ØÎý„m­T.Z„ƒ^xïóž¼OªœðÝâ}Vå׿Á må¾2ãòÄ%VÞ{ŽÞÿä�üŸ¦›�ꆶև9GäR"Îyç;uå/¢Œ¬îÔ¤·‹NÙÚÖeG!Ë–Wh�D¶ŸG·^Pú­ÎôýSyÇ?"P†ž ó��}õý™Â¢P ÎÝyKè£;³uÝ gvn»¡¬¸üÐ¥�<÷Ævûëî‡W\áÑ("ŠÐ<§Ãw‰õSô|7xŸU¹Æ¢´Îù�6ðoŒïáý:›Îßÿì�ÞvûŸ~9YݹA+�²ë{H‘w­+¼í×”>ªs·¿ù#.¿´)¦´¹Q�½Aãû™Uý¡J´ÎðgÐŽ-ðËïíDi†—*¿¶(D²öÛï’óþ3¿]Ôßã;}ò^b߯vûëì‡GL¸þ± U¶ö¿Îê?¬J,½‡ü5×^üýwSMo?´)¦\ïÏ×t5•Ö%–^Ã.?¬BUm½®wçëºÊÏíŠ(†ƒë®·Ö!*¶Þ×*?¬J,½†^hR#L4¿òïÏ×t5ÿóéÿÚ óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ;ëNñ¼·=Ç ']|ç´Ó—¼óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<¢Œsú¨�ÿUüø�»,Ü�óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<òß=ßß,ðÏ1üvûüûÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ; Mxÿ7‹òÿÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<·æ-ÿÐâ]åÿ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<ó üóÏ<óϼóÏ=÷̼ÓÏ<ûÏ<óÏ<óÎ4ãÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÉ‘Óü¾ û9™|5ÿ^Éè«hí­)*5”|¬7óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<³Ï<óϼóÏ<óÏ<óÏ,óÏ\óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<ãÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ4óÏ<óË0“Œ8à ¼óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<òÅ<óÏ<óÎ,±Ã<³ÃóÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óO<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<ó ²Ë,²ÃÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÀM$p<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ cK |óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<ÿß}÷ßóÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏuÛŽ2óÌ0ïO<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óϰï-|ÃŒûç<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏàC nÜóÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ8˾zëý0çÜöÿÿóÓ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<òïìýÓðÃ=Óÿ}Ë~óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÇ<# Ó‰òÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<òN‘€ ÃÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<2Ç8óŽ,Ä,óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<ðÅ@Ê( ‡#Ï<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ¢D¡<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏs�0±„4’Å<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<Í8ñÍ‘L óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<ò 2Œ(ÒJ(“Ï<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÃ’ÂRÃQ�<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ¡‹$SÈñÌ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óË3� Q�óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óO# ÇÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÏ<óÿÄ-!1AQ¡ðaq�‘ѱÁáñP0p `€ ÿÚ?þó3"€ÌÇ, €ØçXà Rç¶9G+LŒ¢àaʼ¢�žƒ„Y`§XJ„–À%ð«YÓEÅá¡°b![fT#*šìÎ\¥Ð,©Î%ÀrbM™PÄéÞ $eTÔPgÊ$-‹ ¼ 0fÃAÀÄ&„1©™À`6¨¬¢zŒXý“’®ªºw0° Ý…ˆk(².iŸ˜œÅP¾¿nðe ¢ô°áp—¤1¬êdñ©Î‚ °÷û‹€!¬Ð̇xÈliã‚ �%(€é ©™’nÏ?h%ø4 #7Ÿ*ë,¹�¨d5¨S�§(l@ˆ¢Äo…¦n™öu'´ºò�Ö�” ãrãuÒ&®úâšp!€¤•P¶ZñˆÍËpËö@ IŽ †l98¡b| �òü"‹œ°äâš|] :r‰HʃÂ@e Å>Šå5áÀBÜ!H `¢#Ô.wÊ­"P|¡0¢€Â Œ‘(<¸~Ü 7Þñ†„ïb�vdq¡€šö* ­ÙöXÀAž…zÌ Ê�ê!+xÃp3€•[×JǦ³žjÿx …‡Eé]ÌÍè´"Ä Ô¸5p°?^æ’ÏJ¿X¨ÌÀˆj¥£¨9EA�u/ÖXFkEâ, Ñ@ I¹ý‚€¿àÿ… �€°Çð[ù0'ôƯo“ªø«™¾>„SÛŠº„Ö›Þ= h­‡¾+Œ7 �¶žð°q^Û<¨<^ˆüLk½Ú’í__^>Л‘ÇßMIAró¯P(5ð:„;S�‚øzÁ+ŸÖûò™s;k¬°*ôùÑõ”c]ãñœÛ{í–ÛÖÜ%‹„ \^›ï°7HjŒŽ€û£ÞŠe×EóÂ\w‡ŸŒ"v½|)hãOšö#‘RŒlaq�Þb 'kfT–1§%c×pÞöá@õßO‰gMõø­(N¬½1:ïGñŒ5®Ûc^_¼¿ä ÿ"ç¼&œWõI B0$OªyŸTó>©æ}SÌú§™õO0P¡8?зLÌ= uÙÄ2-}ø#šËõЫ›e‰XFÌp>�é k;gÊ=xW¥œÝ3è[¦f"f[n÷]„BBÌõëƒð×cð°{ Œ ©w7«½¯‡s “t̡՚Ÿc6”Ø>S`ùMƒå6”Ø>Pf¨TTûŸølŠ­å =Hiø4–ü@Aýù¯¦ûÃP·�ö˜(k`Œ %& –›ÐC_XjÞ%Ã]óó\5þˆ•>wM! ==k¦Ê†„7Ï®ø�öêýàç¤J°X=ïv„ÙnÛúþ†8J‚àgñæPb8‰´½££Š¨F?Š„À9üyƒ‚æ忉xGíÅÅWø@E BüÀ(¸„`|x„ÀÌ5g¿˜Ëù?ÿÄ-!1AQaðq�¡±Ñ‘ÁáPñ`p 0€ ÿÚ?þó)Â,„Éj”Dï‡Ä$‘@šýá VÌFÑjF ÔÕUu¤˜#‹ 5�6­Ø i9Ú±˜ÓR1«S!ˆÀÂP–i˜ù‹* G^bCXŒjÔÈB7„_´,ÐPÔä5…�¸EŽ"%Ê ç¬Gje!ªk”8D›©h´’AÀû~I 0Z§N½àH˜`I!$\9^ªíH3ÍÙ\«ÒR€¬ÌŽd—“,S¢GQÝ à%±‚™̇©ŽÎ=Ýw„�\º>Ó±¯Ä�ùò€Ðœ�À0€èiNµ¸ô¼]Ÿ(Büæ#ˆé �Ⱦôêf‹«ˆ ]£«×áF‡Z¾à@2ëEÙD!ˆ÷/P¬„a,œ©Ò’àr}_™A —W ~D‚ ?è!Púý0àŠä¬$l">~:lá{ucíó´/—ÏÖ7Â�ÕyäçÁ¶íëÑüò_U”…¤t–%ñÞKçnã OzçR¡{®ËŒ$ňއä7™¤-p¢IÔmÓ¤fWë�×I` {§Ç(]zo á1;ÞñšîþûÖ ã1½ûa©|¸þº¨MÌs Á9ޤ|0¸Asƒ8ò]u†Ãxø_8Ã2�–׃Ä9Tvq±Ãõ’…H·zå†Â§WqË ™Þö1ƒ{ÏxÇg�ŽŸtÌ´�Ä$ìßTþj  ‡ íÃñïþUÊb5óßo>‚¥L¢þ§< ˆí?pñ?pñ?pñ?pñ?pñ?pñ ‡‰ ;Ü2àE=~@Åž@®Wƒ8Kx�Ü[>Êa 9q]à õ./•ålg”Ü2?À· „,LÌ‚¡ZΩÖ«Y ­OãWÕö{¬=$€%Ÿp~!”“cA‚Vá‹ÑFfVïú›†Gøµ  =ÄÙdxM‘á6G„ÙdxCf¡ ‡ü6+¾pT­î‘z À;9¦ö1õÅhûyŠ€çç…%‹Â:¸)HÂ]à5|zÀW²ÞüAE ^ñö]¼@HeáET×¹[¯ EÏãÌ&ö>¸ ï?RˆUí÷)½ï˜™ï{Î`9B’j/BP'-ü@bûåjiybŒ4€2´qo{¬%Dé÷â*¨¦Zïî"׿ -ÇEè@0‘.  õ(ÒAz(E¨Ìu'{¬5½ÓØG’Ö„¿üŸÿÄ.!1ðAQ�aÁq‘á`¡ñ 0pÑ@€� ±ÿÚ?ÿÌؽ¯Cn4>zU÷¯ÛHÂuH w‘ý‰Â¬ï¦ Ñõ!½V¡?B\^¨©ní¯ìÉ7|z`€½@+aà| #ÊaWáä 6'_øh¦—8°-÷ýJè!€J¦è“p &Ê®° ;û»ÈRGPÄwHÏP$;f ;³5®‚Kfµ¾‘ŠÅ£v«€(‘qëfÅ$Ž ±’O*Eq5¤Ù޾“`j;´jÜxÁÅH…�ï�K æfG±’“% &|@. !¼ê¾­c)º$‚Ó¾“–âo” 0™u™£@%’‹‡ÌË‚Y ªàfŠrJ0=Ü»…O®û0$Ÿ$ÎÉ!^É鯅e(ó Qî±eY#¿¥H˜Òv&qÝ]qS<6Ÿf�îL�qÇ$3/¹_¨ƒ·OÕ]bQÆ?ÚWí†ÉªšI–Ë,’Ìó/¸.=Ø=ð;ë×�¹ý˜©È*¥‹ºök²JhAˆ¡xêd|IÒu¬œK0} qKŒ2� Ó­y1�Je ûL�²¸ÚYô 2ì)=2vì6’Úb$…D Z ‰œ€Ý�ed€•튖b&üàiãðÌÃÙìšFÇ©› d)迳èq6º BñK“BÅäíÿÃèTÀûÂLuc¤‰Ÿª¢ Ð�•¹Â yD¤ŽærzLšÅöcÓ6&À�3„4¯(DgWôƒl�£'ô°›c–ïž§fÂHzM'¤ä“ºÈ´'ra–ëx-h!)¤“n ˆ¹ bÖMÄ©ôÆLz–‚Å}X]p‡i!_[–h$:BŠÑØS鯅e!))tùä@¼2{˜˜ ¾ñYqꔆ¤”�F™ð†d,™{ 2ÏLllýÓb#sY]Ϋ%ì¿IͨJÖÔB¨é3p'ÂÜ)EýÁÉuA„š°É¥œ}>ÉA$cq%û§aøHI’)ïŽ`ˆP5÷S!%u‡Ò#öÈ}T µödÇn˜`{àï“vE™€ð@ê‡ ©¬ PÁ3í‰]zH¹>ð=âzâòÉDXjLÀ±ëÛ RèR÷Â¥rTÒÖ5íF"U—¤;2 }ÆOxŸÔ‰$:À*„Ô©/p^ÄÖZÃx´„§rNË„�e R´%¾¬F¥F ¿_¨î9ËwÏß‹¡1·<ú0’åµD‹!îëŽÈ¦~Ð…¹ W€ª Zf¸5=¥„Û·é®J±7±–I¯ÜÇîC‘ê©ÉÙé$a#–"�ˆ .Ó­zkáY&¿CÚ q÷&p*eFvU�¢54JŒÓ�ð\&TÔ±ç ,Ȧ„!�á Ó–�A!°–6`lˆu(ÒRR)”�ø1¼§£1€eÌHZ2ÕˆÞŒLçž�GºIô0Á(R„C÷î2g) ´Œ1ªñRâÐ{OØËY4â€ë �‡0 Ô$ïMdåó8 T�]¦zKP]76ù잊¢üDJD¯Õx.,z�HGrž“ƒt0áOv[¸OÔ­$¹Y;Ak龸é}¦G’?ù�:d {�Ýïˆ8¤Š77ì V¨÷MÏLd‰uŠEècœP@*´ÛÓ Å ÜýCðÃh žË0‚C~ Z¤‚Ò‰Ó¶8”Æ„!áÄ\€!BÍÃŽ¿µª=Ós’‚ÄEÅâþøø J*KCöÀ˜]ERÊöÁüôÍc!lQ7ö˵æ„4…£¹Ž²9´ÿ¦W[é.¥€`oNýP2E¡Û$àI‚ tïèïvL „N¢dÃõØRL}W"²RAØ6v�^}8šðw6F=8š¯`ÀÇÖpx©‡S!ˆÖwx”#¿be"©RÂNH;_º¿ùŒ/ =¢Õè@bÒ­Š$ìJöÞ[:C«•hÞXư ( BÅú--P”Û1‡è‡¶IÞ2@Ÿ¡aê8h¬ ¢l:c«Ô„ "LVS`ZBÄî}4ãoGr´~Ø ‡b®Ú©n€úeÛÌB°@ô¶WhUöDñŠ?‹'h‰a÷2.?Ã]KQ¤ß_H„9Ã(”‡]°°Ú«ô AÏY2™ö:d;��öc³QbŠƒöÅë+ä<¸GÄ´�Ò=O׿K¾HÓz0Nf{=˜ÐãØß@O«²‚²# ^*ª" •G@�—ª³Ë1´)U&*Øé�bÖt™””$œá;aJ!Od p)R 1’6¤ŠT”ˆÔDÞD-Cäšœ%S¹¬Õs{÷Å ƱÂU]WÂØOq¹Åe‰&�ì \Io‹2tĈ‡A$H`uÍ|tä°a¦Åà’Õ1Pu(L=؆¨ÈQ‚¥ KCJ ³Ý¦ëÉÉ“ctÉÆ÷Ê)“gId~ {—’‡¢ƒ†–fA¨)'K5B¸$ÀŒh˜–bÂÌ$À6 Ø¢÷úèì>):ƒê”½Ö)#EG–² Odˆåòäª ›Si�™…d#ŠŒ”ž7¢Ö @(I6vXXHŒ8ÁGªð¶¢2A $X™·EDP ,¨‚r×"($n�ðÞØòÚÖã:² „†‰†$v )!Å�ŠI7\XQHqéûKï� »á™E#i#Ø€š„�¼'w@š¶L-P™Œ„n:“y,æ`ù4ÜTI†mËaaLB{˜#VÁ®ìýã!tä²­ß¾P§r‰‘„ÈK¬0ƒ,È£hŒ'Qú÷hŸO@ÙLùôd$Ø’84«@ƒÖ$¹˜b‰]¤³ééÔÄ JÀ÷/ŸèXS4ƒç6#*©Jì´À'Ë:Ê�h=¥¬w] Á:6$ä{ÿ{µ®’(!j ¢Ì0uH“±7¤°a’|Ð!TØ÷¤MEB$ö‚}GסøæÛ¡õ¨FR"÷{ÎÂÃïýå: {Ì^qUtA}$ÃÔ¨Š�UEŸ¹ý†Â•ˆ÷�ÙÁ ¸ ÀìÄ!É�£XÌI¡¤c®³>°c>Q §éú,!�6 „Ý�-a� Ýþé]Œ`ó£¥È$M‹ïþå€4"Š‘C†fz©Õ¼€Qj¯< Ùê+3‚‚!¬£®8H“Ì¢€T¨²BÒ]p ¨Å¢ÙãŠpµ‚•Ø‚°Áä„¥’ƒ"6d€‚â�¤©�¦â4ÆŸ’àfŠ�pq3ãICI>ÌXB”Á0(À²¥I€2|Þ±¤@08²h3pl�=ýŒ= ­!QuûàtE h‹+bƒI•Ä–JÒ‘—36‰ ‘†8XÐG2IH,ÒD38/*t&FÉ‘HS˜ÆŸ§…�’–?tzÂTfŒ�eQ®tôY=DÁ…ÚQJb^ÊÄì'…IÑ}—ØR×EÖ”nØì© hnN¥\ #<Ú认 �dÂPK/ÕdÍA„äêUÁÙÊÔœBZ“ÐBƒ“±Æ¶�ìp€‹#5@ÌÝJ@ ¥„�°v’Ä#¦Ê)ŒLæËÈБ´¬30ÆÒe•ó|(-,´@UzÀ ¼@Á !ÒåŸ]daíT]`§¤ÐàQ\‚Q>ô+¿¤ö†¥ %�@•ƒ†ýa¡˜ªÜê"±¡©Bq ‰ ÜÄ9°rD‚Í‚À€¡ 9Å�ÄU•LàD hzEcnmR‚@K¥ãí°C¨F L#$Û �L ‰  yŒ˜†` ›`ÍÜ©",“'(éK ‚är#vW\ˆÚ(2Ç]Á�Ýæ�VáRô³Ø Iñb%ŽëÇÖý2ï¾&àK Ì“Çód{\˜“$ñúyRÇ d*2dñ¨±vht±ÑK—R0$ai«�¬!`B€°PÀhv-¨°Í`(+F<¼ä“Q5›hJ†Wúy ]µn12Eí­ IÁŸ» Ö-B°�‰PÇŽ§Hx�P„”‡=ãy.¶²°Ñ"bÌkþÄ"© ’Í —@�xÀ‘’Á¨ ÈÙ“¥ :èQ�ßÊBx4™3• ZPé  ßÂd€H j¡g"ŸuÐêQD‰BmacBÉ@$K‚ŒêÁ„(i†dˆÉ#lxÈ)”Ü�-:D KE¦ò{†¦.L � #"-Ÿ$¨0KegMÉß½%©ÇIÇREë· öM` Lj Qó… Œ~�RGP˜D±ÔÆ|€Ur<© HGìÉù‚@Y€˜Ð¦IA‚”JEÜ<�M‰ì”°ˆš&:ÑŒ)p!¦²M1.ì²Iä•o²PÜOaßEÀXÖ:bK\:ѬJú'vInébaS0RÁ@,@€vPË440‹”S#±È½ñ€«sP²:qz`jÈû(„œ’'wÁJCMëX,\‡&¬`¶%"¢6D] ©E–LVɃ$»Äª„±ãÝUñ;“HK)X2û¦Ö)‘²Ǥ!�.PÉ–Ò 6!�…tHh‚¢FáÑ*²"‰½YÙýÙ\L)�ÍF)/aúDß•ìqx¶I€–Ú:ÿjm:B" ‚D# E“¶!l”6#ò�Ât!„14Î&ˆDÞÚ©b‰) !)l¼&  úéš&&«Ü>ᜫã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒåß˾3—|g.øÎ]ñœ»ã9wÆrïŒå_"&«ØR>ëýßÞ=Ÿ‰|(‡V`zà½D‚j™:„ž£&�‚Ô@ýä,äÐÈ —±Ø�=F 'õˆ]r*aTˆƒ‹†$˜¬HD¤@�»õr@’'@dtô²+M$`Çg¯íÝxô^¢Fw€w�“¤¸ Ófˆi�}NN÷`:b 1Q0»`®ÜhT,I–EÅq�ðhSlŽi˜zঠ,‡IšAn D’4”ZLFÌr�¾Œ¾ø$âl$¥é4, Ú“ê™´—qgL0Tvy€0í #a@I@ TG~²¶U1V$MíOŽÞYhÆÂÀ©¡£V¥%-êQ>A¸$:0P¯êãúœ¢$X_|ŠéidHΑˆóãh,�Q_¯íÝxôa#ÀdîNŸs(|Q�‹» ÎèÉ›öFÌ:0IÖ3¤û^�Lf€d „Ò{à ›U&È ýÁÛmÕ OruÅ'ÿu&}÷“+’¨I;0§—ÐAìŽñ!è ²Íur ñQÔÕÇNÙÜ9´‹h·±‰väˆ�z c³*]³,ÞE…ᵈé”Gâmz{¯ÝýgûG÷XèAåHŸgü @Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Áƒ 0`Á‚ГÊb€>Çùj=“¾þFC{‰‡ûáAAA>Éß)­LIþjj ÛûëC>ÿàT5R¶A.Z ^©‹ ß©R¯Ø*;çPu¶�C¢ö¯s,—+³¢;šK³)íY­Ò€E«X�h€’&l«00€Ï;R I%l18²”`ÀJôNóÚom€ßS¢NùA°Ü„uŒÕ Ú4Ð •në¶šÕ²TŸiŸð8a£¼0J&7¸f~š® ô!�--(å)˜’Eß#1yÁ˜™ ·×th¤ Œ€ë®™Åá4 HÐ�á¦Ñ® ÚgL#f-’,-@b»˜ CD˜a]&!éK ¤)Ͷ€¤ßX0Ù_ R» ª“&n))¶è^çô\0à ,€X€6”jçûȈˆˆˆˆˆƒ¤� ¦ÂÝÔê3À²±$J¦ê‘Î]tòPW!0c_m %ŒˆÔn±óZgŸ2Ò)¶8RŸ�¸…Et$C!”Q` kÖ". ƒY�@"<{¥%ÔųdaB¤%ÄBX¶`€G“˜%H2 iRÙ(Q<·ï‡fDøHK%³}ÉÕgÄâU÷0üú²lbD†T0Â…a!‚T)Hè³¹�¦à´µAš*PŽCüÑXBb5� ™¡ÏZ „í1^Gƒ×ä%Õ"ˆˆ¢9;¼€°G¬ß|¡Mæt)*Ñ¿³ÚâAÔ”…I¦Úú€D`ÀìØNS(˜0€B�×d Iƒ¶4wšÃ­`¶P P•¡Š|áNT­èRÈa(‰ú5b(NÃCÅ&ƒRû1’DšÏ ˜ËAK-&g çª X†&ZÎXòèFD$ɨãÔ? d;78¢Êfi@,,•A·Sd(#ZÓY^ÂMìÓÙlÉI ’ã¢tq¢¼¡éb V`”EpÕ–Bõ@(ØAqÿŸ>Í")È&ŠE•FS3`C”hL‰—K¡ŒŠ’܈®&#" “µ$:@!Á“Ó(öÇ', ‹1>çäecž˜È$¨NÓ4qÝ„>²=p!Ö#H³Ù„ö{ð4Ê€„*^ìàõ¥óþ„� 0·ï„m†NÐ cúÛ!À!à 4Ú ó4¸ TOº€  )&P›ÌTP¤ Á’ÖpVòE à!ü�E›#¤‘‰ò7�-x$M�¸^É|¸V�… d׸]B%.�0#—µ ê‚CBZ‚Õ ´“ ˜ fn€�šA 2aA@­1…¿'Ù�é�ãé¯p\”‹H–‰áDB „—p‰›N5ˆn¤�QC�„K˜¤ƒj„Òn°a3Í %ŒˆÔn²5Ðd„I'_¼–±!"pZ%w‘DD_ð ”Ê ŒJ B“ 4UA$35Œˆ‚bzv €ÒB$ʼnxÞNšÀ`%@/ À7#DRõ^r`ã„HŒ¢’ÁÜÅѲŒ”œ’€œ1xÖ’ H�:ÄÁð±’@’èž¹²Xh‰( ǵ—6Ðfc¾7Óx:Ì0q‚–4 Á`o N"ÊH°‘,2¦„2µ¡ijï¿  +R ÌC |áNT­èRÈa6¡››@úßl²@ò;jÂûඃ/+�À††Ã\ bÉpÆba1.Ò^„iÂ"IA$®@AÏ(׿BKc±3 *;쩃¸®ê´}e*VÏ­2Š���f¼¢/aq–"nî¢'äç(@‰Q6^1À Ô”#é€l#Gß>„ÍŠ4ŒÂ@“p"D6�u‡$¼€H C˜bæE u^(™Iñ/P»*EˆL©‰ å P5œèLZ\%WˆÈå�‹Ç d)ðáy‚`$’E©½‰*ƒ“RpÌÂJTH "n(è‘N oXIßx~:Ma2Ó}Z"ŒRrL+)˜/$ ²tU²ŠšÇôªD¨4’J3Ð2Ó¢™ LH²HDa�Èššì’§KP åP ´Ð ”¢¨aJa ¬îOåHBWE¥—œÊRlîHLŽ:­kNቱVA;74dP2Šˆc£{@$€‰X=ÃF #y‰žä˜Ä(š“jÊŠÄ]ÀÁÚÅvý[Gv‰â°�D‰JF–• ß¶V”XH€Y0A„0Âa2¾°ZÈ,£©ârYEn¨Cyúäß�€"¢ÐD JHc !¬¥\$“:dØt’$°OAhŽ ÚÈT‘"4 ŒÂs9‘(;3E߬í¬ðD}í �þØ@v)a:„ªp€íÅŒR-€€:¨e*És EÈÈÀõVIùjI4A–n0ÈG!AGb ¥G,£0dPª•¢ uâ‹A•t;'5f °º¡íé•F‰DÏB�7ë ¼|È ³‘(4FýP(Š_Twˆâã‚ÙuνÌN‚«Æ,‚‹¬Ãм’«¯*�1›˸BÄJ‹–°\@†O” ™ÃÄ ààCÜ@ˆÑ0™£Y1�†~›¦„$H ,¡ß˜1®×ÄbžÄå@Qî­EU ²l»€¡-YÔ�ûö €4ðÊ¢€E € MçN-)JÄ àÆå„iUÑ Á*ƒVˆhõäu°û¾`'Ô²3 à©á±&éT‚‡}¿¢ªªðØ“t‚a®ÙÇ¿4;n¿£ÊfAÒRŠ]÷Î=ù¡ßWœ{óC¾¯<&d!´×|ò™�t”¢—}óÂfAÒ‹MwÏ)™IJ)wßÒ¯ ‰7H ížn•H(wÛ8÷æ‡}_ôUU^n�@,5ÛõâüŸ}?È=_C“ï§ù£žn•H,wß<"4™ €XkóS·nxLIºU ¡ßlðˆÒd‚a¬xlÈ:BA®ùå•6ÈT¦îî§<¶$Ý †Zk{Ý|§-±' ùìÝ¤Úæ^óݻȵDŸkÝ|§) ³*¯–̃¤¤;휷zÿMC<35Ø Š¨ìÏ<6Ê¥»íœŸ}?È=_C“ï§ù£þ]pòØ“t‚i®ùçÙ)ûgŽeR ÷Î_¸k:dí)ž¾ì÷ã°×†ÖÓ š­=¸òÉÑd”²×yðÙ�t„"“]½¸çË’õ9ã�ÙT‚‡}³—îΙ:J'ï{¯”å¡`å÷®0NŽxlIºA ×ð.¿'ßOðSÐäûéþègŽeR öÎ_zãèç–öû€Gê]œðšÚt*KWu®xMm:©«ß<6$Ý*’Xï8m�@,5Û9>ú�z¹É÷Óü�ÑÏ 'ʤ”;ÏŒ†äK g›>{Ôç†TÛ!R»ªŒðÊ›dSWSœ{sC¾¯9³à‡µFr}ôÿõ=O¾Ÿà‡ëÅùó䇽NxlÈ:Ba®Þ•ylÈ:JAC¾ÙÍŸ=ª3“ï§ø©œ¾ÿÂhå±&é"Ó]ó—Þ¸ÀzžŠŒ$3ESb÷¬äûéþêg'ßOðCH{Tz=xÈa>U$¡Þy}댣œõ}™ìÏpß'Þ¿ÓAØÏ-™IH(wÛ<¢æ)»º¸Ï¹†@Bnjî} Ÿ>H{Tg'ßOðC9ó䇽ÏëÕ×çϒךç?ø™V†q³ÿ™•izùòCÚ£8eÿˆN¯9³à‡½NsgÁjŒóÀƒl‚I®Ùå¤ÉT¢—xæÏ‚Õå±&é"Ó]󯢩¡{�VqoÅÚ¯Eü&d!°×lòØ“tªQK¾ù㩎ñP�Gf}ïuòœ´"„p�ÍE Ôêÿ^yàA¶A´×|óÀƒlªQK¾þœ–õÆ ÕÏ,Ív ¦åî{óÃ'E’ÊMgËkiÐMÍV™Çÿ5;vçŒ$3ESB÷¬ó�Â|‚i¬øfk°U4/qÙžYšìM‹Ü÷g”tóGê[—8ÿæ§nÜðÊ›d*CWuQè sòÇò€¹Ç†N‹$!”šÏ?ø™V†q³ÿ™•Ig-Þ¿Û@ÑÎâ«Ï 'ʤ”;φě¤ výwá±&éT‚‡}½,äQžs €„ÜÕÜç'ßOðCÑëÇ ²©ûg†Ä›¤ vÏ)Ég ¦…îwyËï\`�òÞßp(ýK³œóS·nqíÍÚ¬å÷þS@ÑÏ Ég"Äê5YÉ÷¯ôÐv=T ¸_Ïvï"Õxdè²BI¬ùEÌ2 Swuqœõ}™ìÏpßüPíºÏ<6Ê¥»ïúïÏ ²E¦»ç„tóSgî“9}ÿ”Ð4s“ï§ù«œŸ}?ÈðÉÑd„ÃXãßš·Yå³ é)Žûç„tóGê“<²tY%$¡Þy}ÿ„Ð43“ï§ù«œŸ}?Èó�Â|‚I¬ýïuòœ´"Äžn�@,5Û<"4™*�PïÑW„̃¤!šíœŸ}?È=\äûéþ@èç†F“%R ãÆ²ŠMvýyá±&éT‚‡}³ž¯³=™î[óÀƒlªQk¾ÙÉ÷ÓüÔÎO¾Ÿà†yÀÀ6JQK¾ùå9,áX�Nï<¦d%(¥ß|ðÊ›dSUqœ–õÆÔÎ-ø¡ßwœŸ}?À=Läûéþèg%½q€t3›>{Üç†F“%R ãÃbMÒ»ç„̃¤ ížY:,’�PïŸ}?À=Läûéþèg–F“$‹Mcï{¯”å¡`ðØ“t‚a®ß®ü¶$Ý „Rk¶yEÌ2 CWuQœ·zÿMc9>ú�z¹á“¢É%†½>— ùîÝäR¢µî¾S”„Ø‘ËfAÒR ÷ÎKzãêç–F“$‹Mz=/,�IH(w�=LwЦ…î{³Ÿï³=¹îwç!„ùT²×yðÉÑd„ÃXåû†ó®FÐç»ìÏf{†ù}댡žYSl…JnîêsÂkiЩ-]Ö¹ÆþzgLšZ‡Ï ²©E.ûþ¼ðˆÒdªIC¼ùÂC4A'S»Î~ù)ï¾sçÉjŒçÏ’×å¤É"“XòˆÒdªQk¼r}ôÿõ3†_ø„n½å÷þ@ÐÎKzãêg„䳄bu¬ðÌ×`ªn^ã·<¶¶�„ÜÕíž’ÎM‹Ün³ÇSâ±:ŽÌçöNû¼�¢ðÈÒd‚a¬rÝëý´œòšÚtsU®rýÃy×#h xlIºA°×o×~n•H(wÛxm�B)5Û<7%œ*š¸Õzë�^YšìMKÜ÷ç†Ìƒ¤!šïžYšìMKÜ÷ç=ßf{3Ü·ÍwÜžìv%xDi2A°×£\æÏ‚ÕÏß%=µô<6$Ý*’Pï8m�@(5ß9-댩ž[’ÎM‹ÜêóÃbMÒ%±á5´èT–®ë\òœ–p‚(N§Wúñ-‰7H!šížrO•K-wž|¹!íQèÕÇ–ÞË ¥7wZgŒ d!šíœ·zÿMc8ÙÿÌʤ³�ŸüL«C<·%œ*›¹ÕçÚ÷_)ÊBhHƒå¤ÉT¢×xð‹˜d¦®§<²¦Ù ”ÝÝ\z=qíÍÛ¬òˆÒd‚I¬sÝ÷g»‰^S’ÎE ÔêóËbMÒE¦»ç–TÛ!R›»ºœðŽž`(ýApgÜÐíªÏ<6Ê¥»ïúóÏC òE¦±ÏWÝžìvyoo¸~¥ÙÏ,©¶@!75Uå³ é)Žûúë�\r[×/W8·â‡mÖpËÿ�ߡſ;j³“ï§ù£œŸ}?È=\äûéþ@èç†TÛ!R»»œó„†h‚,N§wžxm�B)5Û<¶$Ý*”Zï¶pËÿ�ÖxlIºU ±ß|ðÙ�t„"Ó]óŸ>H{Üþ»ðØ“tªAC¾Ùᵴ覫Lä·®0¦sgÁzœãgÿ3*’ô<2tY! Ö~÷ºùNZF ‰7H ”džN‹$ –ÏÞ÷_)ËB(BËï\`½\äû×ûh9Ëï\`½\å÷®0NŽxDi2A°Ö9ûä§¾ùá9,áX�F«8_Ïvï"•e^€@jjöÏ ½—@@jj´Ï<6Ê¥»ïúóŸ>H{Ôç–̃¤¥»ížn�@(5ß8öæ‡mW¦¹Ï—$=ês—ßøMC<6d! ×|çå�åSŽ7óÛ:äR„8m�@,5Û9>ú�z¹É÷Óü�ÑÏHfˆ"„ê7Yç!„ùT²×yçË’Õå5´è6æ¯|æ»îOv;¼á!š ‹©Ýç'ßOòW9>ú�:9á±&éT‚Ç}óÃbMÒ%†±ÏŸ$=î^/É÷ÓüƒÕô9>ú�:>‹øDi2U$¡Þyùcø@TcÃ'E’ËMgÂ#I’©ñö½×Êr�›2*ùÀÀ6JAc¾ùÉ÷ÓüÔÎO¾Ÿà†r[×'G<²¦Ù„ÜÕÜçÞ÷_)ËB(Bç ÑP�N¯<ð Û „Zk¾xfk°U4/qÙœŸ}?À=Läûéþèg–F“$‹McËbMÒ©e®ñÏŸ$=îÀ+‚ëùlIºA´×|ó�Â|ªYK¼ò[×/W<¢æ)»º¸Î_¸k:dí)Ÿ-™IH(wÛÐä·®0†qoÅÚ¬ñÑÁ|„ÃXç«îÏv;¼p Û €Pk¾r[×/W8öæ‡}^qíÍú¼ðˆÒdªAc¼sòÇð€¸ÇŽd wýx¿'ßOðSÐäûéþègŽeR öÏÈB-5ŽâºÎ~ù)í¯¦¹á0È MUÆxnK8U6/qºÎKzãêg>|�÷©Ï ‰7J¤;ïžQÓÌE©*ð˜“tªAC¾ÙÂþ{7i4¹‡ËfAÒR ÷Ï)‰7H!šíœŸzÿMc8ÿæ§nÜäû×úhÏËÊç[n•J-wÛõß–Ä›¤ŠMvþŠª«�~hvÕzUå3 é)ŽûçüÐï»Ï-‰7H!šíž[n•J-wÛ8÷æ‡}ÞxLÈ:BA®þµUå±&éT¢×}³ËbMÒE&»g”̃¤¤;ïý\{óC¾ïÿ_OÿÙgoogle2fa-laravel/.github/workflows/run-tests.yml000064400000006215152665373430016143 0ustar00name: Tests on: push: paths: - '.github/workflows/run-tests.yml' - 'composer.json' - 'phpunit.xml' - 'src/**' - 'tests/**' pull_request: paths: - '.github/workflows/run-tests.yml' - 'composer.json' - 'phpunit.xml' - 'src/**' - 'tests/**' schedule: - cron: '0 0 * * *' jobs: php-tests: runs-on: ubuntu-22.04 timeout-minutes: 15 env: COMPOSER_NO_INTERACTION: 1 strategy: fail-fast: false matrix: php: [ 8.4, 8.3, 8.2, 8.1, 8.0, 7.4, 7.3, 7.2, ] laravel: [ ^12, ^11, ^10, ^9, ^8, ^7, ^6, ^5.4.36, ] exclude: - php: 8.4 laravel: ^7 - php: 8.4 laravel: ^6 - php: 8.4 laravel: ^5.4.36 - php: 8.3 laravel: ^7 - php: 8.3 laravel: ^6 - php: 8.3 laravel: ^5.4.36 - php: 8.2 laravel: ^7 - php: 8.2 laravel: ^6 - php: 8.2 laravel: ^5.4.36 - php: 8.1 laravel: ^12 - php: 8.1 laravel: ^11 - php: 8.1 laravel: ^7 - php: 8.1 laravel: ^6 - php: 8.1 laravel: ^5.4.36 - php: 8.0 laravel: ^12 - php: 8.0 laravel: ^11 - php: 8.0 laravel: ^10 - php: 8.0 laravel: ^5.4.36 - php: 7.4 laravel: ^12 - php: 7.4 laravel: ^11 - php: 7.4 laravel: ^10 - php: 7.4 laravel: ^9 - php: 7.3 laravel: ^12 - php: 7.3 laravel: ^11 - php: 7.3 laravel: ^10 - php: 7.3 laravel: ^9 - php: 7.2 laravel: ^12 - php: 7.2 laravel: ^11 - php: 7.2 laravel: ^10 - php: 7.2 laravel: ^9 - php: 7.2 laravel: ^8 name: P${{ matrix.php }} L${{ matrix.laravel }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: none extensions: pdo_sqlite, fileinfo - run: composer require laravel/framework:${{ matrix.laravel }} --no-update - name: Get composer cache directory id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer update --prefer-dist --no-plugins - name: phpunit run: vendor/bin/phpunit google2fa-laravel/.travis.yml000064400000002161152665373430012164 0ustar00language: php php: - 7.1 - 7.2 - 7.3 - 7.4 # - nightly ## Cache composer cache: directories: - $HOME/.composer/cache env: - LARAVEL_VERSION=5.8.* TESTBENCH_VERSION=3.8.* - LARAVEL_VERSION=6.* TESTBENCH_VERSION=4.* - LARAVEL_VERSION=7.* TESTBENCH_VERSION=5.* - LARAVEL_VERSION=8.* TESTBENCH_VERSION=6.* matrix: exclude: - php: 7.1 env: LARAVEL_VERSION=6.* TESTBENCH_VERSION=4.* - php: 7.1 env: LARAVEL_VERSION=7.* TESTBENCH_VERSION=5.* - php: 7.1 env: LARAVEL_VERSION=8.* TESTBENCH_VERSION=6.* - php: 7.2 env: LARAVEL_VERSION=8.* TESTBENCH_VERSION=6.* before_script: - yes '' | pecl install imagick - composer require "laravel/framework:${LARAVEL_VERSION}" "orchestra/testbench:${TESTBENCH_VERSION}" --no-update - travis_retry composer update --no-interaction --prefer-dist script: - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover after_script: - | if [[ "$TRAVIS_PHP_VERSION" == '7.4' ]]; then wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover fi