v2.1.4: Merge SSO changes to upgrade to Oauth2.0 (#43)

* Bumps version to 2.1.4
* Updates Pathfinder Database schema to store new AccessTokens
* Updates SSO login flow to work with JWT Access Tokens
* Updates ESI API client dependency to use goryn-clade/pathfinder_esi:v2.1.2
This commit is contained in:
Sam
2021-10-25 02:54:42 +02:00
committed by GitHub
parent 0673759a8d
commit 740aacb539
210 changed files with 267 additions and 91 deletions

View File

@@ -17,6 +17,8 @@ use Exodus4D\Pathfinder\Controller;
use Exodus4D\Pathfinder\Controller\Api as Api;
use Exodus4D\Pathfinder\Model\Pathfinder;
use Exodus4D\Pathfinder\Lib;
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
class Sso extends Api\User{
@@ -42,6 +44,8 @@ class Sso extends Api\User{
const ERROR_CHARACTER_FORBIDDEN = 'Character "%s" is not authorized to log in. Reason: %s';
const ERROR_SERVICE_TIMEOUT = 'CCP SSO service timeout (%ss). Try again later';
const ERROR_COOKIE_LOGIN = 'Login from Cookie failed (data not found). Please retry by CCP SSO';
const ERROR_CCP_JWK_CLAIM = 'Invalid "ENVIRONMENT.[ENVIRONMENT].CCP_SSO_JWK_CLAIM" url. %s';
const ERROR_TOKEN_VERIFICATION = 'Could not validate the authenticity of the Access Token';
/**
* redirect user to CCP SSO page and request authorization
@@ -187,6 +191,7 @@ class Sso extends Api\User{
if(isset($accessData->accessToken, $accessData->esiAccessTokenExpires, $accessData->refreshToken)){
// login succeeded -> get basic character data for current login
$verificationCharacterData = $this->verifyCharacterData($accessData->accessToken);
if( !empty($verificationCharacterData) ){
@@ -196,15 +201,15 @@ class Sso extends Api\User{
// verification available data. Data is needed for "ownerHash" check
// get character data from ESI
$characterData = $this->getCharacterData((int)$verificationCharacterData['characterId']);
$characterData = $this->getCharacterData((int)$verificationCharacterData->characterId);
if( isset($characterData->character) ){
// add "ownerHash" and SSO tokens
$characterData->character['ownerHash'] = $verificationCharacterData['characterOwnerHash'];
$characterData->character['ownerHash'] = $verificationCharacterData->owner;
$characterData->character['esiAccessToken'] = $accessData->accessToken;
$characterData->character['esiAccessTokenExpires'] = $accessData->esiAccessTokenExpires;
$characterData->character['esiRefreshToken'] = $accessData->refreshToken;
$characterData->character['esiScopes'] = $verificationCharacterData['scopes'];
$characterData->character['esiScopes'] = $verificationCharacterData->scp;
// add/update static character data
$characterModel = $this->updateCharacter($characterData);
@@ -422,18 +427,17 @@ class Sso extends Api\User{
}
/**
* verify character data by "access_token"
* -> get some basic information (like character id)
* -> if more character information is required, use ESI "characters" endpoints request instead
* verify character data by decloding JWT "access_token"
* -> verify against CCP JWK
* -> get some basic information (like character id)
* @param string $accessToken
* @return array
* @return object
*/
public function verifyCharacterData(string $accessToken) : array {
$characterData = $this->getF3()->ssoClient()->send('getVerifyCharacter', $accessToken);
public function verifyCharacterData(string $accessToken) : object {
$characterData = $this->verifyJwtAccessToken($accessToken);
if( !empty($characterData) ){
// convert string with scopes to array
$characterData['scopes'] = Lib\Util::convertScopesString($characterData['scopes']);
$characterData->characterId = (int)explode(':',$characterData->sub)[2];
}else{
self::getSSOLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
}
@@ -441,6 +445,46 @@ class Sso extends Api\User{
return $characterData;
}
/**
* verify JWT by comparing to CCP public JWK
* -> get Ccp JWKs
* -> decode accessToken using JWKs
* -> Verify token claim is correct
* @param string $accessToken
* @return object
*/
public function verifyJwtAccessToken(string $accessToken) : object {
$ccpJwks = $this->getCcpJwkData();
// set $leeway in seconds to 10, since sometimes there can be verification errors due server clock skew resulting
// in tokens that look like they were issued 1 second in the future.
JWT::$leeway = 10;
// map list of algs from CCP JWK
$supportedAlgs = array_column($ccpJwks['keys'], 'alg');
// get decoded JWT using ccp supplied JWK
$decodedJwt = JWT::decode($accessToken, JWK::parseKeySet($ccpJwks), $supportedAlgs);
// check if issuer matches correct ccp supplied claim values
if (strpos($decodedJwt->iss, $this->getSsoJwkClaim()) !== true) {
self::getSSOLogger()->write(sprintf(self::ERROR_TOKEN_VERIFICATION, __METHOD__));
}
return $decodedJwt;
}
/**
* get JWK from CCP and return decoded json object
* @return array
*/
protected function getCcpJwkData() : array {
$jwkJson = $this->getF3()->ssoClient()->send('getJWKS');
if( !empty($jwkJson) ){
// ensure items in 'keys' are arrays and not objects
array_walk($jwkJson['keys'], function(&$item){$item = (array) $item;});
return $jwkJson;
}else{
self::getSSOLogger()->write(sprintf(self::ERROR_LOGIN_FAILED, __METHOD__));
}
}
/**
* get character data
* @param int $characterId
@@ -546,6 +590,23 @@ class Sso extends Api\User{
return $url;
}
/**
* get CCP SSO JWK CLAIM from configuration file
* -> throw error if string is missing
* @return string
*/
static function getSsoJwkClaim() : string {
$str = self::getEnvironmentData('CCP_SSO_JWK_CLAIM');
if( empty($str)){
$error = sprintf(self::ERROR_CCP_JWK_CLAIM, __METHOD__);
self::getSSOLogger()->write($error);
\Base::instance()->error(502, $error);
}
return $str;
}
/**
* get logger for SSO logging
* @return \Log

View File

@@ -100,7 +100,7 @@ class CharacterModel extends AbstractPathfinderModel {
'default' => ''
],
'esiAccessToken' => [
'type' => Schema::DT_VARCHAR256
'type' => Schema::DT_TEXT
],
'esiAccessTokenExpires' => [
'type' => Schema::DT_TIMESTAMP,
@@ -1186,13 +1186,13 @@ class CharacterModel extends AbstractPathfinderModel {
$ssoController = new Sso();
if(
!empty( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
$verificationCharacterData['characterId'] === $this->_id
$verificationCharacterData->characterId === $this->_id
){
// get character data from API
$characterData = $ssoController->getCharacterData($this->_id);
if( !empty($characterData->character) ){
$characterData->character['ownerHash'] = $verificationCharacterData['characterOwnerHash'];
$characterData->character['esiScopes'] = $verificationCharacterData['scopes'];
$characterData->character['ownerHash'] = $verificationCharacterData->owner;
$characterData->character['esiScopes'] = $verificationCharacterData->scp;
$this->copyfrom($characterData->character, ['ownerHash', 'esiScopes', 'securityStatus']);
$this->corporationId = $characterData->corporation;

View File

@@ -30,6 +30,7 @@ DB_UNIVERSE_PASS =
CCP_SSO_URL = https://sisilogin.testeveonline.com
CCP_SSO_CLIENT_ID =
CCP_SSO_SECRET_KEY =
CCP_SSO_JWK_CLAIM = login.eveonline.com
CCP_SSO_DOWNTIME = 11:00
; CCP ESI API
@@ -83,6 +84,7 @@ DB_CCP_PASS =
CCP_SSO_URL = https://login.eveonline.com
CCP_SSO_CLIENT_ID =
CCP_SSO_SECRET_KEY =
CCP_SSO_JWK_CLAIM = login.eveonline.com
CCP_SSO_DOWNTIME = 11:00
; CCP ESI API

View File

@@ -13,8 +13,8 @@ NAME = Pathfinder
; Version is used for CSS/JS cache busting and is part of the URL for static resources:
; e.g. public/js/vX.X.X/app.js
; Syntax: String (current version)
; Default: v2.1.3
VERSION = v2.1.3
; Default: v2.1.4
VERSION = v2.1.4
; Contact information [optional]
; Shown on 'licence', 'contact' page.

View File

@@ -44,8 +44,9 @@
"cache/namespaced-cache": "1.1.*",
"react/socket": "1.3.*",
"react/promise-stream": "1.2.*",
"clue/ndjson-react": "1.2.*",
"tyrheimdaleve/pathfinder_esi": "2.1.1"
"clue/ndjson-react": "1.2.*",
"firebase/php-jwt": "^5.4",
"goryn-clade/pathfinder_esi": "2.1.2"
},
"suggest": {
"ext-redis": "Redis can be used as cache backend."

238
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "13caad63b97936ebca523ac384fd4de0",
"content-hash": "1d2297f696ea64b10ef5cdc5ff8688c2",
"packages": [
{
"name": "bcosca/fatfree-core",
@@ -900,6 +900,108 @@
},
"time": "2017-07-23T21:35:13+00:00"
},
{
"name": "firebase/php-jwt",
"version": "v5.4.0",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "d2113d9b2e0e349796e72d2a63cf9319100382d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/d2113d9b2e0e349796e72d2a63cf9319100382d2",
"reference": "d2113d9b2e0e349796e72d2a63cf9319100382d2",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": ">=4.8 <=9"
},
"suggest": {
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"type": "library",
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"jwt",
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v5.4.0"
},
"time": "2021-06-23T19:00:23+00:00"
},
{
"name": "goryn-clade/pathfinder_esi",
"version": "v2.1.2",
"source": {
"type": "git",
"url": "https://github.com/goryn-clade/pathfinder_esi.git",
"reference": "784c9d846a6c7f8f8be016f9f88ed559969d2e2e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/goryn-clade/pathfinder_esi/zipball/784c9d846a6c7f8f8be016f9f88ed559969d2e2e",
"reference": "784c9d846a6c7f8f8be016f9f88ed559969d2e2e",
"shasum": ""
},
"require": {
"cache/void-adapter": "1.0.*",
"caseyamcl/guzzle_retry_middleware": "2.3.*",
"ext-json": "*",
"guzzlehttp/guzzle": "6.5.*",
"php-64bit": ">=7.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Exodus4D\\ESI\\": "app/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sam O'Neill",
"email": "samuel.p.oneill@gmail.com"
}
],
"description": "ESI API library for Pathfinder",
"homepage": "https://github.com/goryn-clade/pathfinder_esi",
"support": {
"issues": "https://github.com/goryn-clade/pathfinder_esi/issues",
"source": "https://github.com/goryn-clade/pathfinder_esi/tree/v2.1.2"
},
"time": "2021-10-25T00:32:18+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.5.5",
@@ -973,16 +1075,16 @@
},
{
"name": "guzzlehttp/promises",
"version": "1.4.1",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d"
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d",
"reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d",
"url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"shasum": ""
},
"require": {
@@ -994,7 +1096,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
},
"autoload": {
@@ -1010,10 +1112,25 @@
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
@@ -1022,22 +1139,36 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/1.4.1"
"source": "https://github.com/guzzle/promises/tree/1.5.1"
},
"time": "2021-03-07T09:25:29+00:00"
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
"type": "tidelift"
}
],
"time": "2021-10-22T20:56:57+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.8.2",
"version": "1.8.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "dc960a912984efb74d0a90222870c72c87f10c91"
"reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91",
"reference": "dc960a912984efb74d0a90222870c72c87f10c91",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/1afdd860a2566ed3c2b0b4a3de6e23434a79ec85",
"reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85",
"shasum": ""
},
"require": {
@@ -1074,13 +1205,34 @@
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
@@ -1097,9 +1249,23 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/1.8.2"
"source": "https://github.com/guzzle/psr7/tree/1.8.3"
},
"time": "2021-04-26T09:17:50+00:00"
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
"type": "tidelift"
}
],
"time": "2021-10-05T13:56:00+00:00"
},
{
"name": "ikkez/f3-cortex",
@@ -2851,50 +3017,6 @@
],
"time": "2021-05-27T09:17:38+00:00"
},
{
"name": "tyrheimdaleve/pathfinder_esi",
"version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/TyrHeimdalEVE/pathfinder_esi.git",
"reference": "79cceecaa33b693149a07ee2d647703bd3369244"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TyrHeimdalEVE/pathfinder_esi/zipball/79cceecaa33b693149a07ee2d647703bd3369244",
"reference": "79cceecaa33b693149a07ee2d647703bd3369244",
"shasum": ""
},
"require": {
"cache/void-adapter": "1.0.*",
"caseyamcl/guzzle_retry_middleware": "2.3.*",
"ext-json": "*",
"guzzlehttp/guzzle": "6.5.*",
"php-64bit": ">=7.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Exodus4D\\ESI\\": "app/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Friedrich",
"email": "pathfinder@exodus4d.de"
}
],
"description": "ESI API library for Pathfinder",
"homepage": "https://github.com/exodus4d/pathfinder_esi",
"support": {
"source": "https://github.com/TyrHeimdalEVE/pathfinder_esi/tree/v2.1.1"
},
"time": "2021-09-07T19:41:45+00:00"
},
{
"name": "xfra35/f3-cron",
"version": "v1.2.1",

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "pathfinder-eve",
"version": "2.1.3",
"version": "2.1.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "pathfinder-eve",
"version": "2.1.3",
"version": "2.1.4",
"engines": {
"node": "12.x"
},

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 917 KiB

After

Width:  |  Height:  |  Size: 917 KiB

View File

Before

Width:  |  Height:  |  Size: 641 KiB

After

Width:  |  Height:  |  Size: 641 KiB

View File

Before

Width:  |  Height:  |  Size: 409 KiB

After

Width:  |  Height:  |  Size: 409 KiB

View File

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

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