diff --git a/.gitignore b/.gitignore index 2b9d4ce1..9f34c408 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ local.env .vagrant composer.phar /nbproject/ -local.*.env +*.env *.aes dockercfg +node_modules/ diff --git a/Dockerfile b/Dockerfile index af1012b3..a68fb93e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,20 +39,24 @@ COPY composer.lock /data/ RUN composer self-update --no-interaction RUN COMPOSER_ALLOW_SUPERUSER=1 composer install --prefer-dist --no-interaction --no-dev --optimize-autoloader --no-scripts --no-progress -# Copy in SSP override files ENV SSP_PATH /data/vendor/simplesamlphp/simplesamlphp + +# Copy modules into simplesamlphp +COPY modules/ $SSP_PATH/modules + +# Copy in SSP override files RUN mv $SSP_PATH/www/index.php $SSP_PATH/www/ssp-index.php COPY dockerbuild/ssp-overrides/index.php $SSP_PATH/www/index.php RUN mv $SSP_PATH/www/saml2/idp/SingleLogoutService.php $SSP_PATH/www/saml2/idp/ssp-SingleLogoutService.php COPY dockerbuild/ssp-overrides/SingleLogoutService.php $SSP_PATH/www/saml2/idp/SingleLogoutService.php COPY dockerbuild/ssp-overrides/saml20-idp-remote.php $SSP_PATH/metadata/saml20-idp-remote.php COPY dockerbuild/ssp-overrides/saml20-sp-remote.php $SSP_PATH/metadata/saml20-sp-remote.php -COPY dockerbuild/ssp-overrides/config.php $SSP_PATH/config/config.php +COPY dockerbuild/config/* $SSP_PATH/config/ COPY dockerbuild/ssp-overrides/id.php $SSP_PATH/www/id.php COPY dockerbuild/ssp-overrides/announcement.php $SSP_PATH/announcement/announcement.php COPY tests /data/tests -RUN cp $SSP_PATH/modules/sildisco/sspoverrides/www_saml2_idp/SSOService.php $SSP_PATH/www/saml2/idp/ +RUN cp $SSP_PATH/modules/sildisco/lib/SSOService.php $SSP_PATH/www/saml2/idp/ RUN chmod a+x /data/run.sh /data/run-tests.sh ADD https://github.com/silinternational/config-shim/releases/latest/download/config-shim.gz config-shim.gz diff --git a/Makefile b/Makefile index 0ae85386..6fd4442f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,5 @@ -start: ssp - -ssp: clean - docker-compose up -d ssp - hub: clean - docker-compose up -d ssp-hub.local ssp-sp1.local sp2 ssp-idp1.local idp2 + docker-compose up -d ssp-hub.local clean: docker-compose kill @@ -21,3 +16,15 @@ test: test-integration: docker-compose run --rm test ./run-integration-tests.sh + +copyJsLib: + cp ./node_modules/@simplewebauthn/browser/dist/bundle/index.umd.min.js ./modules/material/www/simplewebauthn/browser.js + cp ./node_modules/@simplewebauthn/browser/LICENSE.md ./www/simplewebauthn/LICENSE.md + +deps: + docker-compose run --rm node npm install --ignore-scripts + make copyJsLib + +depsupdate: + docker-compose run --rm node npm update --ignore-scripts + make copyJsLib diff --git a/README.md b/README.md index 19c2e4dc..3f908f36 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ must be installed. [Make](https://www.gnu.org/software/make) is optional but simplifies the build process. -[Vagrant](https://www.vagrantup.com) for Windows users. +[PHP](https://www.php.net) and [Composer](https://getcomposer.org) are optional, but at a minimum you need COMPOSER_CACHE_DIR set to a local directory for storing the PHP dependency cache. This must be exported in your local development environment, not in the Docker container environment. For example, in your `~/.bashrc`, include `export COMPOSER_CACHE_DIR="$HOME/.composer"` and create an empty directory at `~/.composer`. ## Configuration By default, configuration is read from environment variables. These are documented @@ -34,9 +34,11 @@ will overwrite variables set in the execution environment. ## Local testing 1. `cp local.env.dist local.env` within project root and make adjustments as needed. -2. Add your github token to the `COMPOSER_AUTH` variable in the `local.env` file. -3. `make` or `docker-compose up -d` within the project root. -4. Visit http://localhost to see SSP running +2. `cp local.broker.env.dist local.broker.env` within project root and make adjustments as needed. +3. Add your github token to the `COMPOSER_AUTH` variable in the `local.env` file. +4. Create `localhost` aliases for `ssp-hub.local`, `ssp-idp1.local`, `ssp-idp2.local`, `ssp-idp3.local`, `ssp-sp1.local`, `ssp-sp2.local`, and `ssp-sp3.local`. This is typically done in `/etc/hosts`. _Example line: `127.0.0.1 ssp-hub.local ssp-idp1.local ssp-idp2.local ssp-idp3.local ssp-sp1.local ssp-sp2.local ssp-sp3.local`_ +4. `make` or `docker-compose up -d` within the project root. +5. Visit http://ssp-hub.local to see SimpleSAMLphp ### Setup PhpStorm for remote debugging with Docker @@ -97,3 +99,376 @@ RUN cd /data/vendor/simplesamlphp/simplesamlphp/modules/material/dictionaries/ov ## Misc. Notes * Use of sildisco's LogUser module is optional and triggered via an authproc. + +## Included Modules + +### ExpiryChecker simpleSAMLphp Module +A simpleSAMLphp module for warning users that their password will expire soon +or that it has already expired. + +**NOTE:** This module does *not* prevent the user from logging in. It merely +shows a warning page (if their password is about to expire), with the option to +change their password now or later, or it tells the user that their password has +already expired, with the only option being to go change their password now. +Both of these pages will be bypassed (for varying lengths of time) if the user +has recently seen one of those two pages, in order to allow the user to get to +the change-password website (assuming it is also behind this IdP). If the user +should not be allowed to log in at all, the simpleSAMLphp Auth. Source should +consider the credentials provided by the user to be invalid. + +The expirychecker module is implemented as an Authentication Processing Filter, +or AuthProc. That means it can be configured in the global config.php file or +the SP remote or IdP hosted metadata. + +It is recommended to run the expirychecker module at the IdP, and configure the +filter to run before all the other filters you may have enabled. + +#### How to use the module + +Set filter parameters in your config. We recommend adding +them to the `'authproc'` array in your `metadata/saml20-idp-hosted.php` file, +but you are also able to put them in the `'authproc.idp'` array in your +`config/config.php` file. + +Example (in `metadata/saml20-idp-hosted.php`): + + 'authproc' => [ + 10 => [ + // Required: + 'class' => 'expirychecker:ExpiryDate', + 'accountNameAttr' => 'cn', + 'expiryDateAttr' => 'schacExpiryDate', + 'passwordChangeUrl' => 'https://idm.example.com/pwdmgr/', + + // Optional: + 'warnDaysBefore' => 14, + 'originalUrlParam' => 'originalurl', + 'dateFormat' => 'm.d.Y', // Use PHP's date syntax. + 'loggerClass' => '\\Sil\\Psr3Adapters\\Psr3SamlLogger', + ], + + // ... + ], + +The `accountNameAttr` parameter represents the SAML attribute name which has +the user's account name stored in it. In certain situations, this will be +displayed to the user, as well as being used in log messages. + +The `expiryDateAttr` parameter represents the SAML attribute name which has +the user's expiry date, which must be formated as YYYYMMDDHHMMSSZ (e.g. +`20111011235959Z`). Those two attributes need to be part of the attribute set +returned when the user successfully authenticates. + +The `warnDaysBefore` parameter should be an integer representing how many days +before the expiry date the "about to expire" warning will be shown to the user. + +The `dateFormat` parameter specifies how you want the date to be formatted, +using PHP `date()` syntax. See . + +The `loggerClass` parameter specifies the name of a PSR-3 compatible class that +can be autoloaded, to use as the logger within ExpiryDate. + +#### Acknowledgements + +This is adapted from the `ssp-iidp-expirycheck` and `expirycheck` modules. +Thanks to Alex Mihičinac, Steve Moitozo, and Steve Bagwell for the initial work +they did on those two modules. + +### Material Module + +Material Design theme for use with SimpleSAMLphp + +#### Installation + +``` +composer.phar require silinternational/simplesamlphp-module-material:dev-master +``` + +#### Configuration + +Update `/simplesamlphp/config/config.php`: + +``` +'theme.use' => 'material:material' +``` + +This project provides a convenience by loading this config with whatever is in the environment variable `THEME_USE`._ + +##### Google reCAPTCHA + +If a site key has been provided in `$this->data['recaptcha.siteKey']`, the +username/password page may require the user prove his/her humanity. + +##### Branding + +Update `/simplesamlphp/config/config.php`: + +``` +'theme.color-scheme' => ['indigo-purple'|'blue_grey-teal'|'red-teal'|'orange-light_blue'|'brown-orange'|'teal-blue'] +``` + +The login page looks for `/simplesamlphp/www/logo.png` which is **NOT** provided by default. + +##### Analytics + +Update `/simplesamlphp/config/config.php`: + +``` +'analytics.trackingId' => 'G-some-unique-id-for-your-site' +``` + +This project provides a convenience by loading this config with whatever is in the environment variable `ANALYTICS_ID`._ + +##### Announcements + +Update `/simplesamlphp/announcement/announcement.php`: + +``` + return 'Some important announcement'; +``` + +By default, the announcement is whatever is returned by `/simplesamlphp/announcement/announcement.php`._ + +If provided, an alert will be shown to the user filled with the content of that announcement. HTML is supported. + +#### Testing the Material theme + +[Manual tests](./docs/material_tests.md) + +#### i18n support + +Translations are categorized by page in definition files located in the `dictionaries` directory. + +Localization is affected by the configuration setting `language.available`. Only language codes found in this property will be utilized. +For example, if a translation is provided in Afrikaans for this module, the configuration must be adjusted to make 'af' an available +language. If that's not done, the translation function will not utilize the translations even if provided. + +### Multi-Factor Authentication (MFA) simpleSAMLphp Module +A simpleSAMLphp module for prompting the user for MFA credentials (such as a +TOTP code, etc.). + +This mfa module is implemented as an Authentication Processing Filter, +or AuthProc. That means it can be configured in the global config.php file or +the SP remote or IdP hosted metadata. + +It is recommended to run the mfa module at the IdP, and configure the +filter to run before all the other filters you may have enabled. + +#### How to use the module + +You will need to set filter parameters in your config. We recommend adding +them to the `'authproc'` array in your `metadata/saml20-idp-hosted.php` file. + +Example (for `metadata/saml20-idp-hosted.php`): + + use Sil\PhpEnv\Env; + use Sil\Psr3Adapters\Psr3SamlLogger; + + // ... + + 'authproc' => [ + 10 => [ + // Required: + 'class' => 'mfa:Mfa', + 'employeeIdAttr' => 'employeeNumber', + 'idBrokerAccessToken' => Env::get('ID_BROKER_ACCESS_TOKEN'), + 'idBrokerAssertValidIp' => Env::get('ID_BROKER_ASSERT_VALID_IP'), + 'idBrokerBaseUri' => Env::get('ID_BROKER_BASE_URI'), + 'idBrokerTrustedIpRanges' => Env::get('ID_BROKER_TRUSTED_IP_RANGES'), + 'idpDomainName' => Env::get('IDP_DOMAIN_NAME'), + 'mfaSetupUrl' => Env::get('MFA_SETUP_URL'), + + // Optional: + 'loggerClass' => Psr3SamlLogger::class, + ], + + // ... + ], + +The `employeeIdAttr` parameter represents the SAML attribute name which has +the user's Employee ID stored in it. In certain situations, this may be +displayed to the user, as well as being used in log messages. + +The `loggerClass` parameter specifies the name of a PSR-3 compatible class that +can be autoloaded, to use as the logger within ExpiryDate. + +The `mfaSetupUrl` parameter is for the URL of where to send the user if they +want/need to set up MFA. + +The `idpDomainName` parameter is used to assemble the Relying Party Origin +(RP Origin) for WebAuthn MFA options. + +#### Why use an AuthProc for MFA? +Based on... + +- the existence of multiple other simpleSAMLphp modules used for MFA and + implemented as AuthProcs, +- implementing my solution as an AuthProc and having a number of tests that all + confirm that it is working as desired, and +- a discussion in the SimpleSAMLphp mailing list about this: + https://groups.google.com/d/msg/simplesamlphp/ocQols0NCZ8/RL_WAcryBwAJ + +... it seems sufficiently safe to implement MFA using a simpleSAMLphp AuthProc. + +For more of the details, please see this Stack Overflow Q&A: +https://stackoverflow.com/q/46566014/3813891 + +#### Acknowledgements +This is adapted from the `silinternational/simplesamlphp-module-mfa` +module, which itself is adapted from other modules. Thanks to all those who +contributed to that work. + +### Profile Review SimpleSAMLphp Module + +A simpleSAMLphp module for prompting the user review their profile (such as +2-step verification, email, etc.). + +This module is implemented as an Authentication Processing Filter, +or AuthProc. That means it can be configured in the global config.php file or +the SP remote or IdP hosted metadata. + +It is recommended to run the profilereview module at the IdP, after all +other authentication modules. + +#### How to use the module + +You will need to set filter parameters in your config. We recommend adding +them to the `'authproc'` array in your `metadata/saml20-idp-hosted.php` file. + +Example (for `metadata/saml20-idp-hosted.php`): + + use Sil\PhpEnv\Env; + use Sil\Psr3Adapters\Psr3SamlLogger; + + // ... + + 'authproc' => [ + 10 => [ + // Required: + 'class' => 'profilereview:ProfileReview', + 'employeeIdAttr' => 'employeeNumber', + 'profileUrl' => Env::get('PROFILE_URL'), + 'mfaLearnMoreUrl' => Env::get('MFA_LEARN_MORE_URL'), + + // Optional: + 'loggerClass' => Psr3SamlLogger::class, + ], + + // ... + ], + +The `employeeIdAttr` parameter represents the SAML attribute name which has +the user's Employee ID stored in it. In certain situations, this may be +displayed to the user, as well as being used in log messages. + +The `loggerClass` parameter specifies the name of a PSR-3 compatible class that +can be autoloaded, to use as the logger within ExpiryDate. + +The `profileUrl` parameter is for the URL of where to send the user if they +want/need to update their profile. + +### SilAuth SimpleSAMLphp module + +SimpleSAMLphp auth module implementing custom business logic: + +- authentication +- rate limiting +- status endpoint + +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/silinternational/simplesamlphp-module-silauth/develop/LICENSE) + +#### Database Migrations +To create another database migration file, run the following (replacing +`YourMigrationName` with whatever you want the migration to be named, using +CamelCase): + + make migration NAME=YourMigrationName + +#### Rate Limiting +SilAuth will rate limit failed logins by username and by every untrusted IP +address from a login attempt. + +##### tl;dr ("the short version") +If there have been more than 10 failed logins for a given username (or IP +address) within the past hour, a captcha will be included in the webpage. The +user may or may not have to directly interact with the captcha, though. + +If there have been more than 50 failed logins for that username (or IP address) +within the past hour, logins for that username (or IP address) will be blocked +for up to an hour. + +##### Details +For each login attempt, if it has too many failed logins within the last hour +(aka. recent failed logins) for the given username OR for any single untrusted +IP address associated with the current request, it will do one of the following: + +- If there are fewer than `Authenticator::REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN` + recent failures: process the request normally. +- If there are at least that many, but fewer than + `Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN`: require the user to pass a + captcha. +- If there are more than that: block that login attempt for `(recent failures + above the limit)^2` seconds after the most recent failed login, with a + minimum of 3 (so blocking for 9 seconds). +- Note: the blocking time is capped at an hour, so if no more failures occur, + then the user will be unblocked in no more than an hour. + +See `features/login.feature` for descriptions of how various situations are +handled. That file not only contains human-readable scenarios, but those are +also actual tests that are run to ensure those descriptions are correct. + +##### Example 1 + +- If `BLOCK_AFTER_NTH_FAILED_LOGIN` is 50, and +- if `REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN` is 10, and +- if there have been 4 failed login attempts for `john_smith`, and +- there have been 10 failed login attempts from `11.22.33.44`, and +- there have been 3 failed login attempts from `192.168.1.2`, and +- someone tries to login as `john_smith` from `192.168.1.2` and their request + goes through a proxy at `11.22.33.44`, then +- they will have to pass a captcha, but they will not yet be blocked. + +##### Example 2 + +- However, if all of the above is true, but +- there have now been 55 failed login attempts from `11.22.33.44`, then +- any request involving that IP address will be blocked for 25 seconds after + the most recent of those failed logins. + +#### Excluding trusted IP addresses from IP address based rate limiting +Since this application enforces rate limits based on the number of recent +failed login attempts by both username and IP address, and since it looks at +both the REMOTE_ADDR and the X-Forwarded-For header for IP addresses, you will +want to list any IP addresses that should NOT be rate limited (such as your +load balancer) in the TRUSTED_IP_ADDRESSES environment variable (see +`local.env.dist`). + +#### Status Check +To check the status of the website, you can access this URL: +`https://(your domain name)/module.php/silauth/status.php` + +### SilDisco module for SAML Discovery + +#### Configuration + +Ensure the DYNAMO_* environment variables are set as shown in the local.env.dist file. + +#### Overview + +[Module Overview](./docs/overview.md) + +#### The Hub + +[The Hub](./docs/the_hub.md) + +#### Authprocs + +[Editing Authprocs](./docs/editing_authprocs.md) + +#### Development + +[Development](./docs/development.md) + +#### Functional Testing + +[Functional Testing](./docs/functional_testing.md) diff --git a/actions-services.yml b/actions-services.yml index 72d4851f..abcd117f 100644 --- a/actions-services.yml +++ b/actions-services.yml @@ -1,17 +1,43 @@ version: '3' services: + + # the db container is used by the silauth module + db: + image: mariadb:10 + environment: + MYSQL_ROOT_PASSWORD: r00tp@ss! + MYSQL_DATABASE: silauth + MYSQL_USER: silauth + MYSQL_PASSWORD: silauth + app: build: . depends_on: - ssp-hub.local - ssp-idp1.local + - ssp-idp2.local + - ssp-idp3.local - ssp-sp1.local + - ssp-sp2.local + - ssp-sp3.local + - pwmanager.local - test-browser + environment: + MYSQL_HOST: db + MYSQL_DATABASE: silauth + MYSQL_USER: silauth + MYSQL_PASSWORD: silauth + PROFILE_URL_FOR_TESTS: http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub + ADMIN_EMAIL: john_doe@there.com + ADMIN_PASS: b + SECRET_SALT: abc123 + IDP_NAME: x volumes: - ./dockerbuild/run-integration-tests.sh:/data/run-integration-tests.sh - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh - ./dockerbuild/run-tests.sh:/data/run-tests.sh - ./features:/data/features + - ./behat.yml:/data/behat.yml - ./tests:/data/tests test-browser: @@ -36,7 +62,6 @@ services: # Enable checking our test metadata - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh - command: /data/run.sh environment: ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "abc123" @@ -51,6 +76,8 @@ services: ssp-idp1.local: build: . + depends_on: + - db volumes: # Utilize custom certs - ./development/idp-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert @@ -63,21 +90,88 @@ services: - ./development/idp-local/metadata/saml20-idp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-hosted.php - ./development/idp-local/metadata/saml20-sp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-sp-remote.php - # Misc. files needed - - ./development/enable-exampleauth-module.sh:/data/enable-exampleauth-module.sh + # Customized SSP code -- TODO: make a better solution that doesn't require hacking SSP code + - ./development/UserPass.php:/data/vendor/simplesamlphp/simplesamlphp/modules/exampleauth/lib/Auth/Source/UserPass.php # Enable checking our test metadata - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh - command: 'bash -c "/data/enable-exampleauth-module.sh && /data/run.sh"' + + # Include the features folder (for the FakeIdBrokerClient class) + - ./features:/data/features + command: > + bash -c "whenavail db 3306 60 /data/vendor/simplesamlphp/simplesamlphp/modules/silauth/lib/Auth/Source/yii migrate --interactive=0 && + /data/run.sh" environment: ADMIN_EMAIL: "john_doe@there.com" ADMIN_PASS: "a" SECRET_SALT: "not-secret-h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "IDP 1" + IDP_DOMAIN_NAME: "mfaidp" + ID_BROKER_ACCESS_TOKEN: "dummy" + ID_BROKER_ASSERT_VALID_IP: "false" + ID_BROKER_BASE_URI: "dummy" + ID_BROKER_TRUSTED_IP_RANGES: "192.168.0.1/8" + MFA_SETUP_URL: "http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub-custom-port" + REMEMBER_ME_SECRET: "12345" + PROFILE_URL: "http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub-custom-port" + PROFILE_URL_FOR_TESTS: "http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub" + SECURE_COOKIE: "false" + SHOW_SAML_ERRORS: "true" + THEME_USE: "default" + MYSQL_HOST: "db" + MYSQL_DATABASE: "silauth" + MYSQL_USER: "silauth" + MYSQL_PASSWORD: "silauth" + BASE_URL_PATH: "http://ssp-idp1.local/" + + ssp-idp2.local: + build: . + volumes: + # Utilize custom certs + - ./development/idp2-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert + + # Utilize custom configs + - ./development/idp2-local/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + - ./development/idp2-local/config/config.php:/data/vendor/simplesamlphp/simplesamlphp/config/config.php + + # Utilize custom metadata + - ./development/idp2-local/metadata/saml20-idp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-hosted.php + - ./development/idp2-local/metadata/saml20-sp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-sp-remote.php + + # Customized SSP code -- TODO: make a better solution that doesn't require hacking SSP code + - ./development/UserPass.php:/data/vendor/simplesamlphp/simplesamlphp/modules/exampleauth/lib/Auth/Source/UserPass.php + + environment: + ADMIN_EMAIL: "john_doe@there.com" + ADMIN_PASS: "b" + SECRET_SALT: "h57fjemb&dn^nsJFGNjweJ" + IDP_NAME: "IDP 2" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" THEME_USE: "material:material" + ssp-idp3.local: + build: . + volumes: + # Utilize custom certs + - ./development/idp3-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert + + # Utilize custom configs + - ./development/idp3-local/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + - ./development/idp3-local/config/config.php:/data/vendor/simplesamlphp/simplesamlphp/config/config.php + + # Utilize custom metadata + - ./development/idp3-local/metadata/saml20-idp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-hosted.php + - ./development/idp3-local/metadata/saml20-sp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-sp-remote.php + + environment: + ADMIN_EMAIL: "john_doe@there.com" + ADMIN_PASS: "c" + SECRET_SALT: "h57fjem34fh*nsJFGNjweJ" + SECURE_COOKIE: "false" + SHOW_SAML_ERRORS: "true" + IDP_NAME: "IdP3" + ssp-sp1.local: build: . volumes: @@ -101,3 +195,108 @@ services: SHOW_SAML_ERRORS: "true" SAML20_IDP_ENABLE: "false" ADMIN_PROTECT_INDEX_PAGE: "false" + + ssp-sp2.local: + build: . + volumes: + # Utilize custom certs + - ./development/sp2-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert + + # Utilize custom configs + - ./development/sp2-local/config/config.php:/data/vendor/simplesamlphp/simplesamlphp/config/config.php + - ./development/sp2-local/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + + # Utilize custom metadata + - ./development/sp2-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php + + environment: + ADMIN_EMAIL: john_doe@there.com + ADMIN_PASS: sp2 + SECRET_SALT: h57fjemb&dn^nsJFGNjweJz2 + SECURE_COOKIE: "false" + SHOW_SAML_ERRORS: "true" + SAML20_IDP_ENABLE: "false" + ADMIN_PROTECT_INDEX_PAGE: "false" + + ssp-sp3.local: + build: . + volumes: + # Utilize custom certs + - ./development/sp3-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert + + # Utilize custom configs + - ./development/sp3-local/config/config.php:/data/vendor/simplesamlphp/simplesamlphp/config/config.php + - ./development/sp3-local/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + + # Utilize custom metadata + - ./development/sp3-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php + + environment: + ADMIN_EMAIL: john_doe@there.com + ADMIN_PASS: sp3 + SECRET_SALT: h57fjemb&dn^nsJFGNjweJz3 + SECURE_COOKIE: "false" + SHOW_SAML_ERRORS: "true" + SAML20_IDP_ENABLE: "false" + ADMIN_PROTECT_INDEX_PAGE: "false" + + + pwmanager.local: + image: silintl/ssp-base:develop + volumes: + # Utilize custom certs + - ./development/sp-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert + + # Utilize custom configs + - ./development/sp-local/config/authsources-pwmanager.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + + # Utilize custom metadata + - ./development/sp-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php + environment: + ADMIN_EMAIL: john_doe@there.com + ADMIN_PASS: sp1 + IDP_NAME: THIS VARIABLE IS REQUIRED BUT PROBABLY NOT USED + SECRET_SALT: NOT-a-secret-k49fjfkw73hjf9t87wjiw + SECURE_COOKIE: "false" + SHOW_SAML_ERRORS: "true" + SAML20_IDP_ENABLE: "false" + ADMIN_PROTECT_INDEX_PAGE: "false" + THEME_USE: default + + # the broker and brokerDb containers are used by the silauth module + broker: + image: silintl/idp-id-broker:latest + depends_on: + - brokerDb + environment: + IDP_NAME: "idp" + MYSQL_HOST: "brokerDb" + MYSQL_DATABASE: "broker" + MYSQL_USER: "user" + MYSQL_PASSWORD: "pass" + EMAIL_SERVICE_accessToken: "dummy" + EMAIL_SERVICE_assertValidIp: "false" + EMAIL_SERVICE_baseUrl: "dummy" + EMAILER_CLASS: Sil\SilIdBroker\Behat\Context\fakes\FakeEmailer + HELP_CENTER_URL: "https://example.org/help" + PASSWORD_FORGOT_URL: "https://example.org/forgot" + PASSWORD_PROFILE_URL: "https://example.org/profile" + SUPPORT_EMAIL: "support@example.org" + EMAIL_SIGNATURE: "one red pill, please" + API_ACCESS_KEYS: "test-cli-abc123" + APP_ENV: "prod" + MFA_TOTP_apiBaseUrl: not_needed_here + MFA_TOTP_apiKey: not_needed_here + MFA_TOTP_apiSecret: not_needed_here + MFA_WEBAUTHN_apiBaseUrl: not_needed_here + MFA_WEBAUTHN_apiKey: not_needed_here + MFA_WEBAUTHN_apiSecret: not_needed_here + command: "bash -c 'whenavail brokerDb 3306 60 ./yii migrate --interactive=0 && ./run.sh'" + + brokerDb: + image: mariadb:10 + environment: + MYSQL_ROOT_PASSWORD: "r00tp@ss!" + MYSQL_DATABASE: "broker" + MYSQL_USER: "user" + MYSQL_PASSWORD: "pass" diff --git a/behat.yml b/behat.yml new file mode 100644 index 00000000..6306126b --- /dev/null +++ b/behat.yml @@ -0,0 +1,33 @@ +default: + suites: + dictionary_features: + paths: [ '%paths.base%//features//dictionary-overrides.feature' ] + contexts: [ 'FeatureContext' ] + expiry_features: + paths: [ '%paths.base%//features//expirychecker.feature' ] + contexts: [ 'ExpiryContext' ] + login_features: + paths: [ '%paths.base%//features//login.feature' ] + contexts: [ 'LoginContext' ] + material_features: + paths: [ '%paths.base%//features//material.feature' ] + contexts: [ 'FeatureContext' ] + mfa_features: + paths: [ '%paths.base%//features//mfa.feature' ] + contexts: [ 'MfaContext' ] + profilereview_features: + paths: [ '%paths.base%//features//profilereview.feature' ] + contexts: [ 'ProfileReviewContext' ] + sildisco_features: + contexts: ['SilDiscoContext'] + paths: + - '%paths.base%//features//Sp1Idp1Sp2Idp2Sp3.feature' + - '%paths.base%//features//Sp1Idp2Sp2Sp3Idp1.feature' + - '%paths.base%//features//Sp2Idp2Sp1Idp1Sp3.feature' + - '%paths.base%//features//Sp2Idp2Sp1Idp2Sp3.feature' + - '%paths.base%//features//Sp3Idp1Sp1Idp1Sp2Idp2.feature' + - '%paths.base%//features//WwwMetadataCept.feature' + - '%paths.base%//features//ZSp1Idp1BetaSp1Idp3.feature' + status_features: + paths: [ '%paths.base%//features//status.feature' ] + contexts: [ 'StatusContext' ] diff --git a/composer.json b/composer.json index b50f58c2..9db562a0 100644 --- a/composer.json +++ b/composer.json @@ -14,17 +14,25 @@ "ext-gmp": "*", "ext-json": "*", "ext-memcached": "*", + "codemix/yii2-streamlog": "^1.3", "simplesamlphp/simplesamlphp": "^1.19.6", "simplesamlphp/composer-module-installer": "1.1.8", - "silinternational/simplesamlphp-module-expirychecker": "^3.1.0", - "silinternational/simplesamlphp-module-silauth": "^7.1.1", - "silinternational/simplesamlphp-module-mfa": "^5.2.1", - "silinternational/simplesamlphp-module-profilereview": "^2.1.0", + "rlanvin/php-ip": "^1.0", "silinternational/ssp-utilities": "^1.1.0", "silinternational/simplesamlphp-module-material": "^8.1.1", "silinternational/simplesamlphp-module-sildisco": "^4.0.0", "silinternational/php-env": "^3.1.0", - "gettext/gettext": "^4.8@dev" + "silinternational/psr3-adapters": "^3.1", + "silinternational/yii2-json-log-targets": "^2.0", + "gettext/gettext": "^4.8@dev", + "silinternational/idp-id-broker-php-client": "^4.3", + "sinergi/browser-detector": "^6.1", + "yiisoft/yii2": "~2.0.12", + "yiisoft/yii2-gii": "^2.0", + "fillup/fake-bower-assets": "^2.0", + "google/recaptcha": "^1.1", + "psr/log": "^1.0", + "monolog/monolog": "^1.22" }, "require-dev": { "behat/behat": "^3.8", @@ -36,7 +44,10 @@ "autoload": { "files": [ "vendor/yiisoft/yii2/Yii.php" - ] + ], + "psr-4": { + "Sil\\SspBase\\Features\\": "features/" + } }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index c987192b..bfda34c2 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "df01bb039aaf6b25519b9e6dea6dae58", + "content-hash": "ae5996cffda6f7fd16dea5eb0e2a1665", "packages": [ { "name": "aws/aws-crt-php", @@ -264,20 +264,20 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.16.0", + "version": "v4.17.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", "shasum": "" }, "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", @@ -319,9 +319,9 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" }, - "time": "2022-09-18T07:06:19+00:00" + "time": "2023-11-17T15:01:25+00:00" }, { "name": "fillup/fake-bower-assets", @@ -1963,106 +1963,6 @@ }, "time": "2019-03-08T08:55:37+00:00" }, - { - "name": "ramsey/uuid", - "version": "3.9.7", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "dc75aa439eb4c1b77f5379fd958b3dc0e6014178" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/dc75aa439eb4c1b77f5379fd958b3dc0e6014178", - "reference": "dc75aa439eb4c1b77f5379fd958b3dc0e6014178", - "shasum": "" - }, - "require": { - "ext-json": "*", - "paragonie/random_compat": "^1 | ^2 | ^9.99.99", - "php": "^5.4 | ^7.0 | ^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "codeception/aspect-mock": "^1 | ^2", - "doctrine/annotations": "^1.2", - "goaop/framework": "1.0.0-alpha.2 | ^1 | >=2.1.0 <=2.3.2", - "mockery/mockery": "^0.9.11 | ^1", - "moontoast/math": "^1.1", - "nikic/php-parser": "<=4.5.0", - "paragonie/random-lib": "^2", - "php-mock/php-mock-phpunit": "^0.3 | ^1.1 | ^2.6", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpunit/phpunit": ">=4.8.36 <9.0.0 | >=9.3.0", - "squizlabs/php_codesniffer": "^3.5", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "ext-ctype": "Provides support for PHP Ctype functions", - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - }, - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - } - ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", - "homepage": "https://github.com/ramsey/uuid", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "rss": "https://github.com/ramsey/uuid/releases.atom", - "source": "https://github.com/ramsey/uuid", - "wiki": "https://github.com/ramsey/uuid/wiki" - }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2022-12-19T21:55:10+00:00" - }, { "name": "rlanvin/php-ip", "version": "v1.0.1", @@ -2794,16 +2694,16 @@ }, { "name": "silinternational/idp-id-broker-php-client", - "version": "4.3.1", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/silinternational/idp-id-broker-php-client.git", - "reference": "c05d01c0ed0666056249bdabd97c0392c99e9790" + "reference": "425955b2699110d6ff9a5d7cf1a15751cc1b8ee9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silinternational/idp-id-broker-php-client/zipball/c05d01c0ed0666056249bdabd97c0392c99e9790", - "reference": "c05d01c0ed0666056249bdabd97c0392c99e9790", + "url": "https://api.github.com/repos/silinternational/idp-id-broker-php-client/zipball/425955b2699110d6ff9a5d7cf1a15751cc1b8ee9", + "reference": "425955b2699110d6ff9a5d7cf1a15751cc1b8ee9", "shasum": "" }, "require": { @@ -2847,9 +2747,9 @@ ], "support": { "issues": "https://github.com/silinternational/idp-id-broker-php-client/issues", - "source": "https://github.com/silinternational/idp-id-broker-php-client/tree/4.3.1" + "source": "https://github.com/silinternational/idp-id-broker-php-client/tree/4.3.2" }, - "time": "2023-06-19T12:59:34+00:00" + "time": "2024-02-28T19:30:43+00:00" }, { "name": "silinternational/php-env", @@ -2942,57 +2842,6 @@ }, "time": "2022-08-24T14:44:38+00:00" }, - { - "name": "silinternational/simplesamlphp-module-expirychecker", - "version": "3.1.3", - "source": { - "type": "git", - "url": "https://github.com/silinternational/simplesamlphp-module-expirychecker.git", - "reference": "2a8f8b18fe60ba3e0e3d7e9c039bcf7347bddb29" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silinternational/simplesamlphp-module-expirychecker/zipball/2a8f8b18fe60ba3e0e3d7e9c039bcf7347bddb29", - "reference": "2a8f8b18fe60ba3e0e3d7e9c039bcf7347bddb29", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.2", - "silinternational/psr3-adapters": "^1.1 || ^2.0 || ^3.0", - "simplesamlphp/simplesamlphp": "~1.17.7 || ~1.18.4 || ~1.19.0" - }, - "require-dev": { - "behat/behat": "^3.3", - "behat/mink": "^1.7", - "behat/mink-goutte-driver": "^1.2", - "phpunit/phpunit": "^8.4", - "roave/security-advisories": "dev-master" - }, - "type": "simplesamlphp-module", - "autoload": { - "psr-4": { - "Sil\\SspExpiryChecker\\": "src/", - "Sil\\SspExpiryChecker\\Behat\\": "features/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1" - ], - "authors": [ - { - "name": "Matt Henderson", - "email": "matt_henderson@sil.org" - } - ], - "description": "simpleSAMLphp module for warning users that their password will expire soon or has already expired.", - "support": { - "issues": "https://github.com/silinternational/simplesamlphp-module-expirychecker/issues", - "source": "https://github.com/silinternational/simplesamlphp-module-expirychecker/tree/3.1.3" - }, - "time": "2023-02-08T15:53:15+00:00" - }, { "name": "silinternational/simplesamlphp-module-material", "version": "8.1.1", @@ -3035,175 +2884,6 @@ }, "time": "2023-06-12T17:37:14+00:00" }, - { - "name": "silinternational/simplesamlphp-module-mfa", - "version": "5.2.1", - "source": { - "type": "git", - "url": "https://github.com/silinternational/simplesamlphp-module-mfa.git", - "reference": "2179f28e5e72e1f14e27d10025cdac5e44b45398" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silinternational/simplesamlphp-module-mfa/zipball/2179f28e5e72e1f14e27d10025cdac5e44b45398", - "reference": "2179f28e5e72e1f14e27d10025cdac5e44b45398", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.2", - "silinternational/idp-id-broker-php-client": "^4.0.0", - "silinternational/php-env": "^2.1 || ^3.0", - "silinternational/psr3-adapters": "^1.1 || ^2.0 || ^3.0", - "simplesamlphp/simplesamlphp": "~1.17.7 || ~1.18.5 || ~1.19.0", - "sinergi/browser-detector": "^6.1" - }, - "require-dev": { - "behat/behat": "^3.3", - "behat/mink": "^1.7", - "behat/mink-goutte-driver": "^1.2", - "phpunit/phpunit": "^8.4", - "roave/security-advisories": "dev-master" - }, - "type": "simplesamlphp-module", - "autoload": { - "psr-4": { - "Sil\\SspMfa\\": "src/", - "Sil\\SspMfa\\Behat\\": "features/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-or-later" - ], - "authors": [ - { - "name": "Matt Henderson", - "email": "matt_henderson@sil.org" - } - ], - "description": "A simpleSAMLphp module for prompting the user for MFA credentials (such as a TOTP code, etc.).", - "support": { - "issues": "https://github.com/silinternational/simplesamlphp-module-mfa/issues", - "source": "https://github.com/silinternational/simplesamlphp-module-mfa/tree/5.2.1" - }, - "time": "2023-06-15T13:38:51+00:00" - }, - { - "name": "silinternational/simplesamlphp-module-profilereview", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/silinternational/simplesamlphp-module-profilereview.git", - "reference": "4c1df2eddcd50147aec198128446c6875c751616" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silinternational/simplesamlphp-module-profilereview/zipball/4c1df2eddcd50147aec198128446c6875c751616", - "reference": "4c1df2eddcd50147aec198128446c6875c751616", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.2", - "silinternational/php-env": "^2.1 || ^3.0", - "silinternational/psr3-adapters": "^1.1 || ^2.0 || ^3.0", - "simplesamlphp/simplesamlphp": "~1.18.6 || ~1.19.0", - "sinergi/browser-detector": "^6.1" - }, - "require-dev": { - "behat/behat": "^3.3", - "behat/mink": "^1.7", - "behat/mink-goutte-driver": "^1.2", - "phpunit/phpunit": "^8.4", - "roave/security-advisories": "dev-master" - }, - "type": "simplesamlphp-module", - "autoload": { - "psr-4": { - "Sil\\SspProfileReview\\": "src/", - "Sil\\SspProfileReview\\Behat\\": "features/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-or-later" - ], - "authors": [ - { - "name": "Matt Henderson", - "email": "matt_henderson@sil.org" - } - ], - "description": "A simpleSAMLphp module for prompting the user to review their profile (such as 2fa, email, etc.).", - "support": { - "issues": "https://github.com/silinternational/simplesamlphp-module-profilereview/issues", - "source": "https://github.com/silinternational/simplesamlphp-module-profilereview/tree/2.1.0" - }, - "time": "2022-09-28T13:50:19+00:00" - }, - { - "name": "silinternational/simplesamlphp-module-silauth", - "version": "7.1.1", - "source": { - "type": "git", - "url": "https://github.com/silinternational/simplesamlphp-module-silauth.git", - "reference": "dbd85e48ffdca86ba1074941a08491fc73c29714" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silinternational/simplesamlphp-module-silauth/zipball/dbd85e48ffdca86ba1074941a08491fc73c29714", - "reference": "dbd85e48ffdca86ba1074941a08491fc73c29714", - "shasum": "" - }, - "require": { - "codemix/yii2-streamlog": "^1.3", - "ext-json": "*", - "fillup/fake-bower-assets": "^2.0", - "google/recaptcha": "^1.1", - "monolog/monolog": "^1.22", - "php": ">=7.0", - "psr/log": "^1.0", - "ramsey/uuid": "^3.5", - "rlanvin/php-ip": "^1.0", - "silinternational/idp-id-broker-php-client": "^4.0.0", - "silinternational/php-env": "^2.0 || ^3.0", - "silinternational/psr3-adapters": "^2.3 || ^3.0", - "silinternational/yii2-json-log-targets": "^2.0", - "simplesamlphp/simplesamlphp": "~1.18.6 || ~1.19.0", - "yiisoft/yii2": "~2.0.12", - "yiisoft/yii2-gii": "^2.0" - }, - "require-dev": { - "behat/behat": "^3.2", - "guzzlehttp/guzzle": "^6.0", - "phpunit/phpunit": "^8.0", - "roave/security-advisories": "dev-master" - }, - "type": "simplesamlphp-module", - "autoload": { - "psr-4": { - "Sil\\SilAuth\\": "src/", - "Sil\\SilAuth\\features\\": "features/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Phillip Shipley", - "email": "phillip.shipley@gmail.com" - } - ], - "description": "SimpleSAMLphp auth module implementing various security measures before calls to IdP ID Broker backend", - "support": { - "issues": "https://github.com/silinternational/simplesamlphp-module-silauth/issues", - "source": "https://github.com/silinternational/simplesamlphp-module-silauth/tree/7.1.1" - }, - "time": "2023-07-31T22:19:59+00:00" - }, { "name": "silinternational/simplesamlphp-module-sildisco", "version": "4.0.0", @@ -6848,16 +6528,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -6865,9 +6545,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6911,7 +6588,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -6927,7 +6604,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php81", @@ -7666,21 +7343,21 @@ }, { "name": "yiisoft/yii2", - "version": "2.0.48.1", + "version": "2.0.49.3", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "de92f154eefe322fc1b1b2a52cce46677441ced4" + "reference": "783f65c9a743dfd7484b6026f1aa6f25e37159d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/de92f154eefe322fc1b1b2a52cce46677441ced4", - "reference": "de92f154eefe322fc1b1b2a52cce46677441ced4", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/783f65c9a743dfd7484b6026f1aa6f25e37159d9", + "reference": "783f65c9a743dfd7484b6026f1aa6f25e37159d9", "shasum": "" }, "require": { "bower-asset/inputmask": "~3.2.2 | ~3.3.5", - "bower-asset/jquery": "3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", + "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", "bower-asset/punycode": "1.3.*", "bower-asset/yii2-pjax": "~2.0.1", "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", @@ -7784,7 +7461,7 @@ "type": "tidelift" } ], - "time": "2023-05-24T19:04:02+00:00" + "time": "2023-10-31T15:39:08+00:00" }, { "name": "yiisoft/yii2-composer", @@ -7953,16 +7630,16 @@ "packages-dev": [ { "name": "behat/behat", - "version": "v3.13.0", + "version": "v3.14.0", "source": { "type": "git", "url": "https://github.com/Behat/Behat.git", - "reference": "9dd7cdb309e464ddeab095cd1a5151c2dccba4ab" + "reference": "2a3832d9cb853a794af3a576f9e524ae460f3340" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Behat/zipball/9dd7cdb309e464ddeab095cd1a5151c2dccba4ab", - "reference": "9dd7cdb309e464ddeab095cd1a5151c2dccba4ab", + "url": "https://api.github.com/repos/Behat/Behat/zipball/2a3832d9cb853a794af3a576f9e524ae460f3340", + "reference": "2a3832d9cb853a794af3a576f9e524ae460f3340", "shasum": "" }, "require": { @@ -7971,18 +7648,18 @@ "ext-mbstring": "*", "php": "^7.2 || ^8.0", "psr/container": "^1.0 || ^2.0", - "symfony/config": "^4.4 || ^5.0 || ^6.0", - "symfony/console": "^4.4 || ^5.0 || ^6.0", - "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", - "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", - "symfony/translation": "^4.4 || ^5.0 || ^6.0", - "symfony/yaml": "^4.4 || ^5.0 || ^6.0" + "symfony/config": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/translation": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^4.4 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { "herrera-io/box": "~1.6.1", "phpspec/prophecy": "^1.15", "phpunit/phpunit": "^8.5 || ^9.0", - "symfony/process": "^4.4 || ^5.0 || ^6.0", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0", "vimeo/psalm": "^4.8" }, "suggest": { @@ -8034,9 +7711,9 @@ ], "support": { "issues": "https://github.com/Behat/Behat/issues", - "source": "https://github.com/Behat/Behat/tree/v3.13.0" + "source": "https://github.com/Behat/Behat/tree/v3.14.0" }, - "time": "2023-04-18T15:40:53+00:00" + "time": "2023-12-09T13:55:02+00:00" }, { "name": "behat/gherkin", @@ -8103,26 +7780,28 @@ }, { "name": "behat/mink", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/minkphp/Mink.git", - "reference": "19e58905632e7cfdc5b2bafb9b950a3521af32c5" + "reference": "d8527fdf8785aad38455fb426af457ab9937aece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/minkphp/Mink/zipball/19e58905632e7cfdc5b2bafb9b950a3521af32c5", - "reference": "19e58905632e7cfdc5b2bafb9b950a3521af32c5", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/d8527fdf8785aad38455fb426af457ab9937aece", + "reference": "d8527fdf8785aad38455fb426af457ab9937aece", "shasum": "" }, "require": { "php": ">=7.2", - "symfony/css-selector": "^4.4 || ^5.0 || ^6.0" + "symfony/css-selector": "^4.4 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.3", "phpunit/phpunit": "^8.5.22 || ^9.5.11", - "symfony/error-handler": "^4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.4 || ^6.0" + "symfony/error-handler": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0" }, "suggest": { "behat/mink-browserkit-driver": "fast headless driver for any app without JS emulation", @@ -8161,9 +7840,9 @@ ], "support": { "issues": "https://github.com/minkphp/Mink/issues", - "source": "https://github.com/minkphp/Mink/tree/v1.10.0" + "source": "https://github.com/minkphp/Mink/tree/v1.11.0" }, - "time": "2022-03-28T14:22:43+00:00" + "time": "2023-12-09T11:23:23+00:00" }, { "name": "behat/mink-extension", @@ -9691,16 +9370,16 @@ }, { "name": "symfony/css-selector", - "version": "v6.3.2", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "883d961421ab1709877c10ac99451632a3d6fa57" + "reference": "1c5d5c2103c3762aff27a27e1e2409e30a79083b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/883d961421ab1709877c10ac99451632a3d6fa57", - "reference": "883d961421ab1709877c10ac99451632a3d6fa57", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c5d5c2103c3762aff27a27e1e2409e30a79083b", + "reference": "1c5d5c2103c3762aff27a27e1e2409e30a79083b", "shasum": "" }, "require": { @@ -9736,7 +9415,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.3.2" + "source": "https://github.com/symfony/css-selector/tree/v6.4.7" }, "funding": [ { @@ -9752,7 +9431,7 @@ "type": "tidelift" } ], - "time": "2023-07-12T16:00:22+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/translation", @@ -10036,5 +9715,5 @@ "ext-memcached": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/development/UserPass.php b/development/UserPass.php new file mode 100644 index 00000000..ebb331d0 --- /dev/null +++ b/development/UserPass.php @@ -0,0 +1,91 @@ +:", + * while the value of each element is a new array with the attributes for each user. + */ + private $users; + + /** + * Constructor for this authentication source. + * + * @param array $info Information about this authentication source. + * @param array $config Configuration. + */ + public function __construct($info, $config) + { + assert(is_array($info)); + assert(is_array($config)); + + // Call the parent constructor first, as required by the interface + parent::__construct($info, $config); + + $this->users = []; + + // Validate and parse our configuration + foreach ($config as $userpass => $attributes) { + if (!is_string($userpass)) { + throw new \Exception( + 'Invalid : for authentication source '.$this->authId.': '.$userpass + ); + } + + $userpass = explode(':', $userpass, 2); + if (count($userpass) !== 2) { + throw new \Exception( + 'Invalid : for authentication source '.$this->authId.': '.$userpass[0] + ); + } + $username = $userpass[0]; + $password = $userpass[1]; + +// try { +// $attributes = \SimpleSAML\Utils\Attributes::normalizeAttributesArray($attributes); +// } catch (\Exception $e) { +// throw new \Exception('Invalid attributes for user '.$username. +// ' in authentication source '.$this->authId.': '.$e->getMessage()); +// } + $this->users[$username.':'.$password] = $attributes; + } + } + + /** + * Attempt to log in using the given username and password. + * + * On a successful login, this function should return the users attributes. On failure, + * it should throw an exception. If the error was caused by the user entering the wrong + * username or password, a \SimpleSAML\Error\Error('WRONGUSERPASS') should be thrown. + * + * Note that both the username and the password are UTF-8 encoded. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * @return array Associative array with the users attributes. + */ + protected function login($username, $password) + { + assert(is_string($username)); + assert(is_string($password)); + + $userpass = $username.':'.$password; + if (!array_key_exists($userpass, $this->users)) { + throw new \SimpleSAML\Error\Error('WRONGUSERPASS'); + } + + return $this->users[$userpass]; + } +} diff --git a/development/enable-exampleauth-module.sh b/development/enable-exampleauth-module.sh deleted file mode 100755 index 5e60e1f7..00000000 --- a/development/enable-exampleauth-module.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -mkdir -p /data/vendor/simplesamlphp/simplesamlphp/modules/exampleauth -touch /data/vendor/simplesamlphp/simplesamlphp/modules/exampleauth/enable diff --git a/development/hub/metadata/idp-remote.php b/development/hub/metadata/idp-remote.php index a729f5e4..56ecd8d6 100644 --- a/development/hub/metadata/idp-remote.php +++ b/development/hub/metadata/idp-remote.php @@ -8,13 +8,13 @@ */ return [ /* - * Guest IdP. Sign in with an "a" (lower case) as the password + * IdP 1 */ 'http://ssp-idp1.local:8085' => [ 'metadata-set' => 'saml20-idp-remote', 'entityid' => 'http://ssp-idp1.local:8085', 'name' => [ - 'en' => 'IDP 1', + 'en' => 'IDP 1:8085', ], 'IDPNamespace' => 'IDP-1-custom-port', 'logoCaption' => 'IDP-1:8085 staff', @@ -26,6 +26,10 @@ 'SingleSignOnService' => 'http://ssp-idp1.local:8085/saml2/idp/SSOService.php', 'SingleLogoutService' => 'http://ssp-idp1.local:8085/saml2/idp/SingleLogoutService.php', 'certData' => 'MIIDzzCCAregAwIBAgIJAPlZYTAQSIbHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIzMTQ1WhcNMjYxMDE3MTIzMTQ1WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArssOaeKbdOQFpN6bBolwSJ/6QFBXA73Sotg60anx9v6aYdUTmi+b7SVtvOmHDgsD5X8pN/6Z11QCZfTYg2nW3ZevGZsj8W/R6C8lRLHzWUr7e7DXKfj8GKZptHlUs68kn0ndNVt9r/+irJe9KBdZ+4kAihykomNdeZg06bvkklxVcvpkOfLTQzEqJAmISPPIeOXes6hXORdqLuRNTuIKarcZ9rstLnpgAs2TE4XDOrSuUg3XFnM05eDpFQpUb0RXWcD16mLCPWw+CPrGoCfoftD5ZGfll+W2wZ7d0kQ4TbCpNyxQH35q65RPVyVNPgSNSsFFkmdcqP9DsFqjJ8YC6wIDAQABo1AwTjAdBgNVHQ4EFgQUD6oyJKOPPhvLQpDCC3027QcuQwUwHwYDVR0jBBgwFoAUD6oyJKOPPhvLQpDCC3027QcuQwUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAA6tCLHJQGfXGdFerQ3J0wUu8YDSLb0WJqPtGdIuyeiywR5ooJf8G/jjYMPgZArepLQSSi6t8/cjEdkYWejGnjMG323drQ9M1sKMUhOJF4po9R3t7IyvGAL3fSqjXA8JXH5MuGuGtChWxaqhduA0dBJhFAtAXQ61IuIQF7vSFxhTwCvJnaWdWD49sG5OqjCfgIQdY/mw70e45rLnR/bpfoigL67sTJxy+Kx2ogbvMR6lITByOEQFMt7BYpMtXrwvKUM7k9NOo1jREmJacC8PTx//jRhCWwzUj1RsfIri24BuITrawwqMsYl8DZiiwMpjUf9m4NPaf4E7+QRpzo+MCcg==', + + // NOTE: This breaks being able to test the hub's authentication sources + // since the hub doesn't create an SP entry in the session + 'SPList' => ['http://ssp-sp1.local', 'http://ssp-sp2.local', 'http://ssp-sp3.local'], ], 'http://ssp-idp1.local' => [ 'metadata-set' => 'saml20-idp-remote', @@ -44,20 +48,24 @@ 'SingleLogoutService' => 'http://ssp-idp1.local/saml2/idp/SingleLogoutService.php', // 'certFingerprint' => 'c9ed4dfb07caf13fc21e0fec1572047eb8a7a4cb' 'certData' => 'MIIDzzCCAregAwIBAgIJAPlZYTAQSIbHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIzMTQ1WhcNMjYxMDE3MTIzMTQ1WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArssOaeKbdOQFpN6bBolwSJ/6QFBXA73Sotg60anx9v6aYdUTmi+b7SVtvOmHDgsD5X8pN/6Z11QCZfTYg2nW3ZevGZsj8W/R6C8lRLHzWUr7e7DXKfj8GKZptHlUs68kn0ndNVt9r/+irJe9KBdZ+4kAihykomNdeZg06bvkklxVcvpkOfLTQzEqJAmISPPIeOXes6hXORdqLuRNTuIKarcZ9rstLnpgAs2TE4XDOrSuUg3XFnM05eDpFQpUb0RXWcD16mLCPWw+CPrGoCfoftD5ZGfll+W2wZ7d0kQ4TbCpNyxQH35q65RPVyVNPgSNSsFFkmdcqP9DsFqjJ8YC6wIDAQABo1AwTjAdBgNVHQ4EFgQUD6oyJKOPPhvLQpDCC3027QcuQwUwHwYDVR0jBBgwFoAUD6oyJKOPPhvLQpDCC3027QcuQwUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAA6tCLHJQGfXGdFerQ3J0wUu8YDSLb0WJqPtGdIuyeiywR5ooJf8G/jjYMPgZArepLQSSi6t8/cjEdkYWejGnjMG323drQ9M1sKMUhOJF4po9R3t7IyvGAL3fSqjXA8JXH5MuGuGtChWxaqhduA0dBJhFAtAXQ61IuIQF7vSFxhTwCvJnaWdWD49sG5OqjCfgIQdY/mw70e45rLnR/bpfoigL67sTJxy+Kx2ogbvMR6lITByOEQFMt7BYpMtXrwvKUM7k9NOo1jREmJacC8PTx//jRhCWwzUj1RsfIri24BuITrawwqMsYl8DZiiwMpjUf9m4NPaf4E7+QRpzo+MCcg==', + + // NOTE: This breaks being able to test the hub's authentication sources + // since the hub doesn't create an SP entry in the session + 'SPList' => ['http://ssp-sp1.local', 'http://ssp-sp2.local', 'http://ssp-sp3.local'], ], /* - * IdP2. Sign in with a "b" (lower case) as the password + * IdP 2 */ 'http://ssp-idp2.local:8086' => [ 'metadata-set' => 'saml20-idp-remote', 'entityid' => 'http://ssp-idp2.local:8086', 'name' => [ - 'en' => 'IDP 2', + 'en' => 'IDP 2:8086', ], 'IDPNamespace' => 'IDP-2-custom-port', 'logoCaption' => 'IDP-2:8086 staff', - 'enabled' => false, + 'enabled' => true, 'betaEnabled' => true, 'logoURL' => 'https://dummyimage.com/125x125/0f4fbd/ffffff.png&text=IDP+2+8086', @@ -66,6 +74,9 @@ 'SingleSignOnService' => 'http://ssp-idp2.local:8086/saml2/idp/SSOService.php', 'SingleLogoutService' => 'http://ssp-idp2.local:8086/saml2/idp/SingleLogoutService.php', 'certData' => 'MIIDzzCCAregAwIBAgIJALBaUrvz1X5DMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE4MTQwMDUxWhcNMjYxMDE4MTQwMDUxWjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx5mZNwjEnakJho+5etuFyx+2g9rs96iLX/LDC24aBAsdNxTNuIc1jJ7pxBxGrepEND4LkietLNBlOr1q50nq2+ddTrCfmoJB+9BqBOxcm9qWeqWbp8/arUjaxPzK3DfZrxJxIVFjzqFF7gI91y9yvEW/fqLRMhvnH1ns+N1ne59zr1y6h9mmHfBffGr1YXAfyEAuV1ich4AfTfjqhdwFwxhFLLCVnxA0bDbNw/0eGCSiA13N7a013xTurLeJu0AQaZYssMqvc/17UphH4gWDMEZAwy0EfRSBOsDOYCxeNxVajnWX1834VDpBDfpnZj996Gh8tzRQxQgT9/plHKhGiwIDAQABo1AwTjAdBgNVHQ4EFgQUApxlUQg26GrG3eH8lEG3SkqbH/swHwYDVR0jBBgwFoAUApxlUQg26GrG3eH8lEG3SkqbH/swDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANhbm8WgIqBDlF7DIRVUbq04TEA9nOJG8wdjJYdoKrPX9f/E9slkFuD2StcK99RTcowa8Z2OmW7tksa+onyH611Lq21QXh4aHzQUAm2HbsmPQRZnkByeYoCJ/1tuEho+x+VGanaUICSBVWYiebAQVKHR6miFypRElibNBizm2nqp6Q9B87V8COzyDVngR1DlWDduxYaNOBgvht3Rk9Y2pVHqym42dIfN+pprcsB1PGBkY/BngIuS/aqTENbmoC737vcb06e8uzBsbCpHtqUBjPpL2psQZVJ2Y84JmHafC3B7nFQrjdZBbc9eMHfPo240Rh+pDLwxdxPqRAZdeLaUkCQ==', + + // limit which Sps can use this IdP + 'SPList' => ['http://ssp-sp1.local:8081', 'http://ssp-sp2.local:8082'], ], 'http://ssp-idp2.local' => [ 'metadata-set' => 'saml20-idp-remote', @@ -75,7 +86,7 @@ ], 'IDPNamespace' => 'IDP-2', 'logoCaption' => 'IDP-2 staff', - 'enabled' => false, + 'enabled' => true, 'betaEnabled' => true, 'logoURL' => 'https://dummyimage.com/125x125/0f4fbd/ffffff.png&text=IDP+2', @@ -84,5 +95,49 @@ 'SingleSignOnService' => 'http://ssp-idp2.local/saml2/idp/SSOService.php', 'SingleLogoutService' => 'http://ssp-idp2.local/saml2/idp/SingleLogoutService.php', 'certData' => 'MIIDzzCCAregAwIBAgIJALBaUrvz1X5DMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE4MTQwMDUxWhcNMjYxMDE4MTQwMDUxWjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx5mZNwjEnakJho+5etuFyx+2g9rs96iLX/LDC24aBAsdNxTNuIc1jJ7pxBxGrepEND4LkietLNBlOr1q50nq2+ddTrCfmoJB+9BqBOxcm9qWeqWbp8/arUjaxPzK3DfZrxJxIVFjzqFF7gI91y9yvEW/fqLRMhvnH1ns+N1ne59zr1y6h9mmHfBffGr1YXAfyEAuV1ich4AfTfjqhdwFwxhFLLCVnxA0bDbNw/0eGCSiA13N7a013xTurLeJu0AQaZYssMqvc/17UphH4gWDMEZAwy0EfRSBOsDOYCxeNxVajnWX1834VDpBDfpnZj996Gh8tzRQxQgT9/plHKhGiwIDAQABo1AwTjAdBgNVHQ4EFgQUApxlUQg26GrG3eH8lEG3SkqbH/swHwYDVR0jBBgwFoAUApxlUQg26GrG3eH8lEG3SkqbH/swDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANhbm8WgIqBDlF7DIRVUbq04TEA9nOJG8wdjJYdoKrPX9f/E9slkFuD2StcK99RTcowa8Z2OmW7tksa+onyH611Lq21QXh4aHzQUAm2HbsmPQRZnkByeYoCJ/1tuEho+x+VGanaUICSBVWYiebAQVKHR6miFypRElibNBizm2nqp6Q9B87V8COzyDVngR1DlWDduxYaNOBgvht3Rk9Y2pVHqym42dIfN+pprcsB1PGBkY/BngIuS/aqTENbmoC737vcb06e8uzBsbCpHtqUBjPpL2psQZVJ2Y84JmHafC3B7nFQrjdZBbc9eMHfPo240Rh+pDLwxdxPqRAZdeLaUkCQ==', + + // limit which Sps can use this IdP + 'SPList' => ['http://ssp-sp1.local', 'http://ssp-sp2.local'], + ], + + /* + * IdP 3 + */ + 'http://ssp-idp3.local:8087' => [ + 'metadata-set' => 'saml20-idp-remote', + 'entityid' => 'http://ssp-idp3.local:8087', + 'name' => [ + 'en' => 'IDP 3:8087', + ], + 'IDPNamespace' => 'IDP-3-custom-port', + 'logoCaption' => 'IDP-3:8087 staff', + 'enabled' => false, + 'betaEnabled' => true, + 'logoURL' => 'https://dummyimage.com/125x125/0f4fbd/ffffff.png&text=IDP+3+8087', + + 'description' => 'Local IDP3 for testing SSP Hub (custom port)', + + 'SingleSignOnService' => 'http://ssp-idp3.local:8087/saml2/idp/SSOService.php', + 'SingleLogoutService' => 'http://ssp-idp3.local:8087/saml2/idp/SingleLogoutService.php', + 'certData' => 'MIIDzzCCAregAwIBAgIJALBaUrvz1X5DMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE4MTQwMDUxWhcNMjYxMDE4MTQwMDUxWjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx5mZNwjEnakJho+5etuFyx+2g9rs96iLX/LDC24aBAsdNxTNuIc1jJ7pxBxGrepEND4LkietLNBlOr1q50nq2+ddTrCfmoJB+9BqBOxcm9qWeqWbp8/arUjaxPzK3DfZrxJxIVFjzqFF7gI91y9yvEW/fqLRMhvnH1ns+N1ne59zr1y6h9mmHfBffGr1YXAfyEAuV1ich4AfTfjqhdwFwxhFLLCVnxA0bDbNw/0eGCSiA13N7a013xTurLeJu0AQaZYssMqvc/17UphH4gWDMEZAwy0EfRSBOsDOYCxeNxVajnWX1834VDpBDfpnZj996Gh8tzRQxQgT9/plHKhGiwIDAQABo1AwTjAdBgNVHQ4EFgQUApxlUQg26GrG3eH8lEG3SkqbH/swHwYDVR0jBBgwFoAUApxlUQg26GrG3eH8lEG3SkqbH/swDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANhbm8WgIqBDlF7DIRVUbq04TEA9nOJG8wdjJYdoKrPX9f/E9slkFuD2StcK99RTcowa8Z2OmW7tksa+onyH611Lq21QXh4aHzQUAm2HbsmPQRZnkByeYoCJ/1tuEho+x+VGanaUICSBVWYiebAQVKHR6miFypRElibNBizm2nqp6Q9B87V8COzyDVngR1DlWDduxYaNOBgvht3Rk9Y2pVHqym42dIfN+pprcsB1PGBkY/BngIuS/aqTENbmoC737vcb06e8uzBsbCpHtqUBjPpL2psQZVJ2Y84JmHafC3B7nFQrjdZBbc9eMHfPo240Rh+pDLwxdxPqRAZdeLaUkCQ==', + ], + 'http://ssp-idp3.local' => [ + 'metadata-set' => 'saml20-idp-remote', + 'entityid' => 'http://ssp-idp3.local', + 'name' => [ + 'en' => 'IDP 3', + ], + 'IDPNamespace' => 'IDP-3', + 'logoCaption' => 'IDP-3 staff', + 'enabled' => false, + 'betaEnabled' => true, + 'logoURL' => 'https://dummyimage.com/125x125/0f4fbd/ffffff.png&text=IDP+3', + + 'description' => 'Local IDP3 for testing SSP Hub', + + 'SingleSignOnService' => 'http://ssp-idp3.local/saml2/idp/SSOService.php', + 'SingleLogoutService' => 'http://ssp-idp3.local/saml2/idp/SingleLogoutService.php', + 'certData' => 'MIIDzzCCAregAwIBAgIJALBaUrvz1X5DMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE4MTQwMDUxWhcNMjYxMDE4MTQwMDUxWjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx5mZNwjEnakJho+5etuFyx+2g9rs96iLX/LDC24aBAsdNxTNuIc1jJ7pxBxGrepEND4LkietLNBlOr1q50nq2+ddTrCfmoJB+9BqBOxcm9qWeqWbp8/arUjaxPzK3DfZrxJxIVFjzqFF7gI91y9yvEW/fqLRMhvnH1ns+N1ne59zr1y6h9mmHfBffGr1YXAfyEAuV1ich4AfTfjqhdwFwxhFLLCVnxA0bDbNw/0eGCSiA13N7a013xTurLeJu0AQaZYssMqvc/17UphH4gWDMEZAwy0EfRSBOsDOYCxeNxVajnWX1834VDpBDfpnZj996Gh8tzRQxQgT9/plHKhGiwIDAQABo1AwTjAdBgNVHQ4EFgQUApxlUQg26GrG3eH8lEG3SkqbH/swHwYDVR0jBBgwFoAUApxlUQg26GrG3eH8lEG3SkqbH/swDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANhbm8WgIqBDlF7DIRVUbq04TEA9nOJG8wdjJYdoKrPX9f/E9slkFuD2StcK99RTcowa8Z2OmW7tksa+onyH611Lq21QXh4aHzQUAm2HbsmPQRZnkByeYoCJ/1tuEho+x+VGanaUICSBVWYiebAQVKHR6miFypRElibNBizm2nqp6Q9B87V8COzyDVngR1DlWDduxYaNOBgvht3Rk9Y2pVHqym42dIfN+pprcsB1PGBkY/BngIuS/aqTENbmoC737vcb06e8uzBsbCpHtqUBjPpL2psQZVJ2Y84JmHafC3B7nFQrjdZBbc9eMHfPo240Rh+pDLwxdxPqRAZdeLaUkCQ==', ], + ]; diff --git a/development/hub/metadata/sp-remote.php b/development/hub/metadata/sp-remote.php index 988ebec7..68f6974e 100644 --- a/development/hub/metadata/sp-remote.php +++ b/development/hub/metadata/sp-remote.php @@ -10,26 +10,28 @@ * Example SimpleSAMLphp SAML 2.0 SP */ 'http://ssp-sp1.local:8081' => [ + 'name' => "SP1 (custom port)", + 'AssertionConsumerService' => 'http://ssp-sp1.local:8081/module.php/saml/sp/saml2-acs.php/ssp-hub-custom-port', + 'SingleLogoutService' => 'http://ssp-sp1.local:8081/module.php/saml/sp/saml2-logout.php/ssp-hub-custom-port', + 'certData' => 'MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYosgyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/cesppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoKGjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBgdk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1AwTjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYcMbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+XrgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVwPUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298HuvsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejtFBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkhT0DHCOw67GP97MWzceyFw+n9Vg==', 'IDPList' => [ 'http://ssp-idp1.local:8085', 'http://ssp-idp2.local:8086', + 'http://ssp-idp3.local:8087', ], - 'name' => "SP Local", - 'AssertionConsumerService' => 'http://ssp-sp1.local:8081/module.php/saml/sp/saml2-acs.php/ssp-hub-custom-port', - 'SingleLogoutService' => 'http://ssp-sp1.local:8081/module.php/saml/sp/saml2-logout.php/ssp-hub-custom-port', - 'certData' => 'MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYosgyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/cesppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoKGjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBgdk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1AwTjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYcMbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+XrgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVwPUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298HuvsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejtFBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkhT0DHCOw67GP97MWzceyFw+n9Vg==', 'assertion.encryption' => true, ], 'http://ssp-sp1.local' => [ + 'name' => "SP1", + 'AssertionConsumerService' => 'http://ssp-sp1.local/module.php/saml/sp/saml2-acs.php/ssp-hub', + 'SingleLogoutService' => 'http://ssp-sp1.local/module.php/saml/sp/saml2-logout.php/ssp-hub', + 'certData' => 'MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYosgyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/cesppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoKGjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBgdk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1AwTjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYcMbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+XrgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVwPUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298HuvsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejtFBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkhT0DHCOw67GP97MWzceyFw+n9Vg==', 'IDPList' => [ 'http://ssp-idp1.local', 'http://ssp-idp2.local', + 'http://ssp-idp3.local', ], - 'name' => "SP Local", - 'AssertionConsumerService' => 'http://ssp-sp1.local/module.php/saml/sp/saml2-acs.php/ssp-hub', - 'SingleLogoutService' => 'http://ssp-sp1.local/module.php/saml/sp/saml2-logout.php/ssp-hub', - 'certData' => 'MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYosgyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/cesppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoKGjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBgdk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1AwTjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYcMbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+XrgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVwPUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298HuvsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejtFBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkhT0DHCOw67GP97MWzceyFw+n9Vg==', 'assertion.encryption' => true, ], @@ -39,7 +41,7 @@ 'IDPList' => [ 'http://ssp-idp2.local:8086', ], - 'name' => 'SP 2 (custom port)', + 'name' => 'SP2 (custom port)', 'certData' => 'MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYosgyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/cesppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoKGjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBgdk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1AwTjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYcMbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+XrgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVwPUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298HuvsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejtFBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkhT0DHCOw67GP97MWzceyFw+n9Vg==', 'assertion.encryption' => true, ], @@ -50,7 +52,35 @@ 'IDPList' => [ 'http://ssp-idp2.local', ], - 'name' => 'SP 2', + 'name' => 'SP2', + 'certData' => 'MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYosgyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/cesppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoKGjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBgdk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1AwTjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYcMbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+XrgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVwPUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298HuvsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejtFBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkhT0DHCOw67GP97MWzceyFw+n9Vg==', + 'assertion.encryption' => true, + ], + + // for test purposes, SP3 should be on the SPList entry of idp2 + + 'http://ssp-sp3.local:8083' => [ + 'AssertionConsumerService' => 'http://ssp-sp3.local:8083/module.php/saml/sp/saml2-acs.php/ssp-hub', + 'SingleLogoutService' => 'http://ssp-sp3.local:8083/module.php/saml/sp/saml2-logout.php/ssp-hub', + 'IDPList' => [ + 'http://ssp-idp1.local:8085', + 'http://ssp-idp2.local:8086', // overruled by Idp2 + 'http://ssp-idp3.local:8087' + ], + 'name' => 'SP3 (custom port)', + 'certData' => 'MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYosgyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/cesppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoKGjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBgdk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1AwTjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYcMbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+XrgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVwPUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298HuvsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejtFBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkhT0DHCOw67GP97MWzceyFw+n9Vg==', + 'assertion.encryption' => true, + ], + + 'http://ssp-sp3.local' => [ + 'AssertionConsumerService' => 'http://ssp-sp3.local/module.php/saml/sp/saml2-acs.php/ssp-hub', + 'SingleLogoutService' => 'http://ssp-sp3.local/module.php/saml/sp/saml2-logout.php/ssp-hub', + 'IDPList' => [ + 'http://ssp-idp1.local', + 'http://ssp-idp2.local', // overruled by Idp2 + 'http://ssp-idp3.local' + ], + 'name' => 'SP3', 'certData' => 'MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYosgyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/cesppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoKGjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBgdk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1AwTjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYcMbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+XrgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVwPUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298HuvsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejtFBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkhT0DHCOw67GP97MWzceyFw+n9Vg==', 'assertion.encryption' => true, ], diff --git a/development/hub/run-debug.sh b/development/hub/run-debug.sh index bcb746ea..6be1b1e3 100755 --- a/development/hub/run-debug.sh +++ b/development/hub/run-debug.sh @@ -11,7 +11,6 @@ echo "xdebug.remote_enable=1" >> $INI_FILE echo "xdebug.remote_host=$XDEBUG_REMOTE_HOST" >> $INI_FILE mkdir -p /data/vendor/simplesamlphp/simplesamlphp/modules/sildisco -touch /data/vendor/simplesamlphp/simplesamlphp/modules/sildisco/default-enable # now the builtin run script can be started /data/run.sh diff --git a/development/idp-local/config/authsources.php b/development/idp-local/config/authsources.php index 3424be4b..9971fe63 100644 --- a/development/idp-local/config/authsources.php +++ b/development/idp-local/config/authsources.php @@ -1,5 +1,7 @@ [ 'exampleauth:UserPass', + + // expirychecker test user whose password expires in the distant future 'distant_future:a' => [ 'eduPersonPrincipalName' => ['DISTANT_FUTURE@ssp-idp1.local'], 'sn' => ['Future'], @@ -20,31 +24,1205 @@ 'mail' => ['distant_future@example.com'], 'employeeNumber' => ['11111'], 'cn' => ['DISTANT_FUTURE'], + 'mfa' => [ + 'prompt' => 'no', + ], 'schacExpiryDate' => [ gmdate('YmdHis\Z', strtotime('+6 months')), // Distant future ], ], - 'near_future:a' => [ + + // expirychecker test user whose password expires in the near future + 'near_future:b' => [ 'eduPersonPrincipalName' => ['NEAR_FUTURE@ssp-idp1.local'], 'sn' => ['Future'], 'givenName' => ['Near'], 'mail' => ['near_future@example.com'], 'employeeNumber' => ['22222'], 'cn' => ['NEAR_FUTURE'], + 'mfa' => [ + 'prompt' => 'no', + ], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+3 days')), // Soon but not tomorrow + ], + ], + + // expirychecker test user whose password expires in one day + 'next_day:a' => [ + 'eduPersonPrincipalName' => ['NEXT_DAY@ssp-hub-idp2.local'], + 'eduPersonTargetID' => ['22888888-2222-2222-2222-222222222222'], + 'sn' => ['Day'], + 'givenName' => ['Next'], + 'mail' => ['next_day@example.com'], + 'employeeNumber' => ['22888'], + 'cn' => ['NEXT_DAY'], 'schacExpiryDate' => [ gmdate('YmdHis\Z', strtotime('+1 day')), // Very soon ], ], - 'already_past:a' => [ + + // expirychecker test user whose password expires in the past + 'already_past:c' => [ 'eduPersonPrincipalName' => ['ALREADY_PAST@ssp-idp1.local'], 'sn' => ['Past'], 'givenName' => ['Already'], 'mail' => ['already_past@example.com'], 'employeeNumber' => ['33333'], 'cn' => ['ALREADY_PAST'], + 'mfa' => [ + 'prompt' => 'no', + ], 'schacExpiryDate' => [ gmdate('YmdHis\Z', strtotime('-1 day')), // In the past ], ], + + // expirychecker test user whose password expiry is missing + 'missing_exp:d' => [ + 'eduPersonPrincipalName' => ['MISSING_EXP@ssp-idp-1.local'], + 'sn' => ['Expiration'], + 'givenName' => ['Missing'], + 'mail' => ['missing_exp@example.com'], + 'employeeNumber' => ['44444'], + 'cn' => ['MISSING_EXP'], + ], + + // expirychecker test user whose password expiry is invalid + 'invalid_exp:e' => [ + 'eduPersonPrincipalName' => ['INVALID_EXP@ssp-idp-1.local'], + 'sn' => ['Expiration'], + 'givenName' => ['Invalid'], + 'mail' => ['invalid_exp@example.com'], + 'employeeNumber' => ['55555'], + 'cn' => ['INVALID_EXP'], + 'mfa' => [ + 'prompt' => 'no', + ], + 'schacExpiryDate' => [ + 'invalid' + ], + ], + + // profilereview test user whose profile is not due for review + 'no_review:e' => [ + 'eduPersonPrincipalName' => ['NO_REVIEW@idp'], + 'eduPersonTargetID' => ['11111111-1111-1111-1111-111111111111'], + 'sn' => ['Review'], + 'givenName' => ['No'], + 'mail' => ['no_review@example.com'], + 'employeeNumber' => ['11111'], + 'cn' => ['NO_REVIEW'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'mfa' => [ + 'prompt' => 'no', + 'add' => 'no', + 'options' => [ + [ + 'id' => 111, + 'type' => 'backupcode', + 'label' => '2SV #1', + 'created_utc' => '2017-10-24T20:40:47Z', + 'last_used_utc' => null, + 'data' => [ + 'count' => 10 + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + ], + 'profile_review' => 'no' + ], + + // profilereview test user whose profile is flagged for mfa_add review + 'mfa_add:f' => [ + 'eduPersonPrincipalName' => ['MFA_ADD@idp'], + 'eduPersonTargetID' => ['22222222-2222-2222-2222-222222222222'], + 'sn' => ['Add'], + 'givenName' => ['Mfa'], + 'mail' => ['mfa_add@example.com'], + 'employeeNumber' => ['22222'], + 'cn' => ['MFA_ADD'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'mfa' => [ + 'prompt' => 'no', + 'add' => 'yes', + 'options' => [], + ], + 'method' => [ + 'add' => 'no', + ], + 'profile_review' => 'no' + ], + + // profilereview test user whose profile is flagged for method_add review + 'method_add:g' => [ + 'eduPersonPrincipalName' => ['METHOD_ADD@methodidp'], + 'eduPersonTargetID' => ['44444444-4444-4444-4444-444444444444'], + 'sn' => ['Add'], + 'givenName' => ['Method'], + 'mail' => ['method_add@example.com'], + 'employeeNumber' => ['44444'], + 'cn' => ['METHOD_ADD'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'mfa' => [ + 'prompt' => 'no', + 'add' => 'no', + 'options' => [ + [ + 'id' => 444, + 'type' => 'backupcode', + 'label' => '2SV #1', + 'created_utc' => '2017-10-24T20:40:47Z', + 'last_used_utc' => null, + 'data' => [ + 'count' => 10 + ], + ], + ], + ], + 'method' => [ + 'add' => 'yes', + ], + 'profile_review' => 'no' + ], + + // profilereview test user whose profile is flagged for profile review + 'profile_review:h' => [ + 'eduPersonPrincipalName' => ['METHOD_REVIEW@methodidp'], + 'eduPersonTargetID' => ['55555555-5555-5555-5555-555555555555'], + 'sn' => ['Review'], + 'givenName' => ['Method'], + 'mail' => ['method_review@example.com'], + 'employeeNumber' => ['55555'], + 'cn' => ['METHOD_REVIEW'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'mfa' => [ + 'prompt' => 'no', + 'add' => 'no', + 'options' => [ + [ + 'id' => 555, + 'type' => 'backupcode', + 'label' => '2SV #1', + 'created_utc' => '2017-10-24T20:40:47Z', + 'last_used_utc' => null, + 'data' => [ + 'count' => 10 + ], + ], + [ + 'id' => 556, + 'type' => 'manager', + 'label' => '2SV #2', + 'created_utc' => '2017-10-24T20:40:47Z', + 'last_used_utc' => '2017-10-24T20:41:57Z', + 'data' => [ + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [ + [ + 'id' => '55555555555555555555555555555555', + 'value' => 'method@example.com', + 'verified' => true, + 'created' => '2017-10-24T20:40:47Z', + ], + ], + ], + 'profile_review' => 'yes' + ], + + // mfa test user who does not require mfa + 'no_mfa_needed:a' => [ + 'eduPersonPrincipalName' => ['NO_MFA_NEEDED@mfaidp'], + 'eduPersonTargetID' => ['11111111-1111-1111-1111-111111111111'], + 'sn' => ['Needed'], + 'givenName' => ['No MFA'], + 'mail' => ['no_mfa_needed@example.com'], + 'employeeNumber' => ['11111'], + 'cn' => ['NO_MFA_NEEDED'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'no', + 'add' => 'no', + 'options' => [], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa to be set up + 'must_set_up_mfa:a' => [ + 'eduPersonPrincipalName' => ['MUST_SET_UP_MFA@mfaidp'], + 'eduPersonTargetID' => ['22222222-2222-2222-2222-222222222222'], + 'sn' => ['Set Up MFA'], + 'givenName' => ['Must'], + 'mail' => ['must_set_up_mfa@example.com'], + 'employeeNumber' => ['22222'], + 'cn' => ['MUST_SET_UP_MFA'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has backup codes + 'has_backupcode:a' => [ + 'eduPersonPrincipalName' => ['HAS_BACKUPCODE@mfaidp'], + 'eduPersonTargetID' => ['33333333-3333-3333-3333-333333333333'], + 'sn' => ['Backupcode'], + 'givenName' => ['Has'], + 'mail' => ['has_backupcode@example.com'], + 'employeeNumber' => ['33333'], + 'cn' => ['HAS_BACKUPCODE'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '7', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has backup codes and a manager email + 'has_backupcode_and_mgr:a' => [ + 'eduPersonPrincipalName' => ['HAS_BACKUPCODE@mfaidp'], + 'eduPersonTargetID' => ['33333333-3333-3333-3333-333333333333'], + 'sn' => ['Backupcode'], + 'givenName' => ['Has'], + 'mail' => ['has_backupcode@example.com'], + 'employeeNumber' => ['33333'], + 'cn' => ['HAS_BACKUPCODE'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '7', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who requires mfa and has totp + 'has_totp:a' => [ + 'eduPersonPrincipalName' => ['HAS_TOTP@mfaidp'], + 'eduPersonTargetID' => ['44444444-4444-4444-4444-444444444444'], + 'sn' => ['TOTP'], + 'givenName' => ['Has'], + 'mail' => ['has_totp@example.com'], + 'employeeNumber' => ['44444'], + 'cn' => ['HAS_TOTP'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '2', + 'type' => 'totp', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has totp and a manager email + 'has_totp_and_mgr:a' => [ + 'eduPersonPrincipalName' => ['HAS_TOTP@mfaidp'], + 'eduPersonTargetID' => ['44444444-4444-4444-4444-444444444444'], + 'sn' => ['TOTP'], + 'givenName' => ['Has'], + 'mail' => ['has_totp@example.com'], + 'employeeNumber' => ['44444'], + 'cn' => ['HAS_TOTP'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '2', + 'type' => 'totp', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who requires mfa and has a webauthn + 'has_webauthn:a' => [ + 'eduPersonPrincipalName' => ['HAS_WEBAUTHN@mfaidp'], + 'eduPersonTargetID' => ['55555555-5555-5555-5555-555555555555'], + 'sn' => ['WebAuthn'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn@example.com'], + 'employeeNumber' => ['55555'], + 'cn' => ['HAS_WEBAUTHN'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '3', + 'type' => 'webauthn', + 'label' => 'Blue security key (work)', + 'created_utc' => '2017-10-24T20:40:57Z', + 'last_used_utc' => null, + 'data' => [ + // Response from "POST /webauthn/login" MFA API call. + ], + ], + ] + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has webauthn and a manager email + 'has_webauthn_and_mgr:a' => [ + 'eduPersonPrincipalName' => ['HAS_WEBAUTHN@mfaidp'], + 'eduPersonTargetID' => ['55555555-5555-5555-5555-555555555555'], + 'sn' => ['WebAuthn'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn@example.com'], + 'employeeNumber' => ['55555'], + 'cn' => ['HAS_WEBAUTHN'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '3', + 'type' => 'webauthn', + 'data' => '', + ], + ] + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who requires mfa and has all forms of mfa + 'has_all:a' => [ + 'eduPersonPrincipalName' => ['has_all@mfaidp'], + 'eduPersonTargetID' => ['77777777-7777-7777-7777-777777777777'], + 'sn' => ['All'], + 'givenName' => ['Has'], + 'mail' => ['has_all@example.com'], + 'employeeNumber' => ['777777'], + 'cn' => ['HAS_ALL'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '1', + 'type' => 'backupcode', + 'data' => [ + 'count' => 8, + ], + ], + [ + 'id' => '2', + 'type' => 'totp', + 'data' => '', + ], + [ + 'id' => '3', + 'type' => 'webauthn', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who has a rate-limited mfa + 'has_rate_limited_mfa:a' => [ + 'eduPersonPrincipalName' => ['HAS_RATE_LIMITED_MFA@mfaidp'], + 'eduPersonTargetID' => ['88888888-8888-8888-8888-888888888888'], + 'sn' => ['Rate-Limited MFA'], + 'givenName' => ['Has'], + 'mail' => ['has_rate_limited_mfa@example.com'], + 'employeeNumber' => ['88888'], + 'cn' => ['HAS_RATE_LIMITED_MFA'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => 987, //FakeIdBrokerClient::RATE_LIMITED_MFA_ID, + 'type' => 'backupcode', + 'data' => [ + 'count' => 5, + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has 4 backup codes + 'has_4_backupcodes:a' => [ + 'eduPersonPrincipalName' => ['HAS_4_BACKUPCODES@mfaidp'], + 'eduPersonTargetID' => ['99999999-9999-9999-9999-999999999999'], + 'sn' => ['Backupcodes'], + 'givenName' => ['Has 4'], + 'mail' => ['has_4_backupcodes@example.com'], + 'employeeNumber' => ['99999'], + 'cn' => ['HAS_4_BACKUPCODES'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '90', + 'type' => 'backupcode', + 'data' => [ + 'count' => 4, + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has 1 backup code remaining + 'has_1_backupcode_only:a' => [ + 'eduPersonPrincipalName' => ['HAS_1_BACKUPCODE_ONLY@mfaidp'], + 'eduPersonTargetID' => ['00000010-0010-0010-0010-000000000010'], + 'sn' => ['Only, And No Other MFA'], + 'givenName' => ['Has 1 Backupcode'], + 'mail' => ['has_1_backupcode_only@example.com'], + 'employeeNumber' => ['00010'], + 'cn' => ['HAS_1_BACKUPCODE_ONLY'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '100', + 'type' => 'backupcode', + 'data' => [ + 'count' => 1, + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has one backup code plus another option + 'has_1_backupcode_plus:a' => [ + 'eduPersonPrincipalName' => ['HAS_1_BACKUPCODE_PLUS@mfaidp'], + 'eduPersonTargetID' => ['00000011-0011-0011-0011-000000000011'], + 'sn' => ['Plus Other MFA'], + 'givenName' => ['Has 1 Backupcode'], + 'mail' => ['has_1_backupcode_plus@example.com'], + 'employeeNumber' => ['00011'], + 'cn' => ['HAS_1_BACKUPCODE_PLUS'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '110', + 'type' => 'backupcode', + 'data' => [ + 'count' => 1, + ], + ], + [ + 'id' => '112', + 'type' => 'totp', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has webauthn and totp + 'has_webauthn_totp:a' => [ + 'eduPersonPrincipalName' => ['has_webauthn_totp@mfaidp'], + 'eduPersonTargetID' => ['00000012-0012-0012-0012-000000000012'], + 'sn' => ['WebAuthn And TOTP'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn_totp@example.com'], + 'employeeNumber' => ['00012'], + 'cn' => ['HAS_WEBAUTHN_TOTP'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '120', + 'type' => 'totp', + 'data' => '', + ], + [ + 'id' => '121', + 'type' => 'webauthn', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has webauthn, totp and a manager email + 'has_webauthn_totp_and_mgr:a' => [ + 'eduPersonPrincipalName' => ['has_webauthn_totp@mfaidp'], + 'eduPersonTargetID' => ['00000012-0012-0012-0012-000000000012'], + 'sn' => ['WebAuthn And TOTP'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn_totp@example.com'], + 'employeeNumber' => ['00012'], + 'cn' => ['HAS_WEBAUTHN_TOTP'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '120', + 'type' => 'totp', + 'data' => '', + ], + [ + 'id' => '121', + 'type' => 'webauthn', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who requires mfa and has webauthn and backup codes + 'has_webauthn_backupcodes:a' => [ + 'eduPersonPrincipalName' => ['has_webauthn_backupcodes@mfaidp'], + 'eduPersonTargetID' => ['00000013-0013-0013-0013-000000000013'], + 'sn' => ['WebAuthn And Backup Codes'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn_backupcodes@example.com'], + 'employeeNumber' => ['00013'], + 'cn' => ['HAS_WEBAUTHN_BACKUPCODES'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '130', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + [ + 'id' => '131', + 'type' => 'webauthn', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has backup codes and a manager email + 'has_webauthn_backupcodes_and_mgr:a' => [ + 'eduPersonPrincipalName' => ['has_webauthn_backupcodes@mfaidp'], + 'eduPersonTargetID' => ['00000013-0013-0013-0013-000000000013'], + 'sn' => ['WebAuthn And Backup Codes'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn_backupcodes@example.com'], + 'employeeNumber' => ['00013'], + 'cn' => ['HAS_WEBAUTHN_BACKUPCODES'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '130', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + [ + 'id' => '131', + 'type' => 'webauthn', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who requires mfa and has totp and backup codes + 'has_webauthn_totp_backupcodes:a' => [ + 'eduPersonPrincipalName' => ['has_webauthn_totp_backupcodes@mfaidp'], + 'eduPersonTargetID' => ['00000014-0014-0014-0014-000000000014'], + 'sn' => ['WebAuthn, TOTP, And Backup Codes'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn_totp_backupcodes@example.com'], + 'employeeNumber' => ['00014'], + 'cn' => ['HAS_WEBAUTHN_TOTP_BACKUPCODES'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '140', + 'type' => 'totp', + 'data' => '', + ], + [ + 'id' => '141', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + [ + 'id' => '142', + 'type' => 'webauthn', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has backup codes, totp, and a manager email + 'has_webauthn_totp_backupcodes_and_mgr:a' => [ + 'eduPersonPrincipalName' => ['has_webauthn_totp_backupcodes@mfaidp'], + 'eduPersonTargetID' => ['00000014-0014-0014-0014-000000000014'], + 'sn' => ['WebAuthn, TOTP, And Backup Codes'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn_totp_backupcodes@example.com'], + 'employeeNumber' => ['00014'], + 'cn' => ['HAS_WEBAUTHN_TOTP_BACKUPCODES'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '140', + 'type' => 'totp', + 'data' => '', + ], + [ + 'id' => '141', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + [ + 'id' => '142', + 'type' => 'webauthn', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who requires mfa and has manager code, webauthn, and a more-recently used totp + 'has_mgr_code_webauthn_and_more_recently_used_totp:a' => [ + 'eduPersonPrincipalName' => ['has_mgr_code_webauthn_and_more_recently_used_totp@mfaidp'], + 'eduPersonTargetID' => ['00000114-0014-0014-0014-000000000014'], + 'sn' => ['Manager Code, WebAuthn, More Recently Used TOTP'], + 'givenName' => ['Has'], + 'mail' => ['has_mgr_code_webauthn_and_more_recently_used_totp@example.com'], + 'employeeNumber' => ['00114'], + 'cn' => ['HAS_MGR_CODE_WEBAUTHN_AND_MORE_RECENTLY_USED_TOTP'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '1140', + 'type' => 'totp', + 'last_used_utc' => '2011-01-01T00:00:00Z', + 'data' => '', + ], + [ + 'id' => '1141', + 'type' => 'webauthn', + 'last_used_utc' => '2000-01-01T00:00:00Z', + 'data' => '', + ], + [ + 'id' => '1142', + 'type' => 'manager', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who requires mfa and has webauthn and more recently used totp + 'has_webauthn_and_more_recently_used_totp:a' => [ + 'eduPersonPrincipalName' => ['has_webauthn_and_more_recently_used_totp@mfaidp'], + 'eduPersonTargetID' => ['00000214-0014-0014-0014-000000000014'], + 'sn' => ['WebAuthn And More Recently Used TOTP'], + 'givenName' => ['Has'], + 'mail' => ['has_webauthn_and_more_recently_used_totp@example.com'], + 'employeeNumber' => ['00214'], + 'cn' => ['HAS_WEBAUTHN_AND_MORE_RECENTLY_USED_TOTP'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '2140', + 'type' => 'totp', + 'last_used_utc' => '2011-01-01T00:00:00Z', + 'data' => '', + ], + [ + 'id' => '2141', + 'type' => 'webauthn', + 'last_used_utc' => '2000-01-01T00:00:00Z', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has totp and more recently used webauthn + 'has_totp_and_more_recently_used_webauthn:a' => [ + 'eduPersonPrincipalName' => ['has_totp_and_more_recently_used_webauthn@mfaidp'], + 'eduPersonTargetID' => ['00000314-0014-0014-0014-000000000014'], + 'sn' => ['TOTP And More Recently Used Webauthn'], + 'givenName' => ['Has'], + 'mail' => ['has_totp_and_more_recently_used_webauthn@example.com'], + 'employeeNumber' => ['00314'], + 'cn' => ['HAS_TOTP_AND_MORE_RECENTLY_USED_WEBAUTHN'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '3140', + 'type' => 'totp', + 'last_used_utc' => '2000-01-01T00:00:00Z', + 'data' => '', + ], + [ + 'id' => '3141', + 'type' => 'webauthn', + 'last_used_utc' => '2011-01-01T00:00:00Z', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has totp and more recently-used backup code + 'has_totp_and_more_recently_used_backup_code:a' => [ + 'eduPersonPrincipalName' => ['has_totp_and_more_recently_used_backup_code@mfaidp'], + 'eduPersonTargetID' => ['00000414-0014-0014-0014-000000000014'], + 'sn' => ['TOTP And More Recently Used Backup Code'], + 'givenName' => ['Has'], + 'mail' => ['has_totp_and_more_recently_used_backup_code@example.com'], + 'employeeNumber' => ['00414'], + 'cn' => ['HAS_TOTP_AND_MORE_RECENTLY_USED_BACKUP_CODE'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '4140', + 'type' => 'totp', + 'last_used_utc' => '2000-01-01T00:00:00Z', + 'data' => '', + ], + [ + 'id' => '4141', + 'type' => 'backupcode', + 'last_used_utc' => '2011-01-01T00:00:00Z', + 'data' => [ + 'count' => 10, + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has backup code and a more recently used totp + 'has_backup_code_and_more_recently_used_totp:a' => [ + 'eduPersonPrincipalName' => ['has_backup_code_and_more_recently_used_totp@mfaidp'], + 'eduPersonTargetID' => ['00000514-0014-0014-0014-000000000014'], + 'sn' => ['Backup Code And More Recently Used TOTP'], + 'givenName' => ['Has'], + 'mail' => ['has_backup_code_and_more_recently_used_totp@example.com'], + 'employeeNumber' => ['00514'], + 'cn' => ['HAS_BACKUP_CODE_AND_MORE_RECENTLY_USED_TOTP'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '5140', + 'type' => 'backupcode', + 'last_used_utc' => '2000-01-01T00:00:00Z', + 'data' => [ + 'count' => 10, + ], + ], + [ + 'id' => '5141', + 'type' => 'totp', + 'last_used_utc' => '2011-01-01T00:00:00Z', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has totp and backup codes + 'has_totp_backupcodes:a' => [ + 'eduPersonPrincipalName' => ['has_totp_backupcodes@mfaidp'], + 'eduPersonTargetID' => ['00000015-0015-0015-0015-000000000015'], + 'sn' => ['TOTP And Backup Codes'], + 'givenName' => ['Has'], + 'mail' => ['has_totp_backupcodes@example.com'], + 'employeeNumber' => ['00015'], + 'cn' => ['HAS_TOTP_BACKUPCODES'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '150', + 'type' => 'totp', + 'data' => '', + ], + [ + 'id' => '151', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + ], + + // mfa test user who requires mfa and has totp, backup codes, and manager email + 'has_totp_backupcodes_and_mgr:a' => [ + 'eduPersonPrincipalName' => ['has_totp_backupcodes@mfaidp'], + 'eduPersonTargetID' => ['00000015-0015-0015-0015-000000000015'], + 'sn' => ['TOTP And Backup Codes'], + 'givenName' => ['Has'], + 'mail' => ['has_totp_backupcodes@example.com'], + 'employeeNumber' => ['00015'], + 'cn' => ['HAS_TOTP_BACKUPCODES'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '150', + 'type' => 'totp', + 'data' => '', + ], + [ + 'id' => '151', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // mfa test user who requires mfa and has backup codes and manager code + 'has_mgr_code:a' => [ + 'eduPersonPrincipalName' => ['has_mgr_code@mfaidp'], + 'eduPersonTargetID' => ['00000015-0015-0015-0015-000000000015'], + 'sn' => ['Manager Code'], + 'givenName' => ['Has'], + 'mail' => ['has_mgr_code@example.com'], + 'employeeNumber' => ['00015'], + 'cn' => ['HAS_MGR_CODE'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'profile_review' => 'no', + 'mfa' => [ + 'prompt' => 'yes', + 'add' => 'no', + 'options' => [ + [ + 'id' => '151', + 'type' => 'backupcode', + 'data' => [ + 'count' => 10, + ], + ], + [ + 'id' => '152', + 'type' => 'manager', + 'data' => '', + ], + ], + ], + 'method' => [ + 'add' => 'no', + 'options' => [], + ], + 'manager_email' => ['manager@example.com'], + ], + + // sildisco test user + 'sildisco_idp1:sildisco_password' => [ + 'eduPersonPrincipalName' => ['sildisco@idp1'], + 'eduPersonTargetID' => ['57de1930-c5d2-4f6f-9318-d85a939c45d8'], + 'sn' => ['IDP1'], + 'givenName' => ['SilDisco'], + 'mail' => ['sildisco_idp1@example.com'], + 'employeeNumber' => ['50001'], + 'cn' => ['SILDISCO_IDP1'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'mfa' => [ + 'prompt' => 'no', + 'add' => 'no', + 'options' => [], + ], + 'method' => [ + 'add' => 'no', + ], + 'profile_review' => 'no' + ], + ], ]; diff --git a/development/idp-local/config/config.php b/development/idp-local/config/config.php index 685f66f1..db650c58 100644 --- a/development/idp-local/config/config.php +++ b/development/idp-local/config/config.php @@ -599,6 +599,7 @@ 'profilereview' => true, 'silauth' => true, 'sildisco' => true, + 'exampleauth' => true, ], diff --git a/development/idp-local/metadata/saml20-idp-hosted.php b/development/idp-local/metadata/saml20-idp-hosted.php index 55d541b4..323fec43 100644 --- a/development/idp-local/metadata/saml20-idp-hosted.php +++ b/development/idp-local/metadata/saml20-idp-hosted.php @@ -1,4 +1,8 @@ [ 10 => [ + 'class' => 'mfa:Mfa', + 'employeeIdAttr' => 'employeeNumber', + 'idBrokerAccessToken' => Env::get('ID_BROKER_ACCESS_TOKEN'), + 'idBrokerAssertValidIp' => Env::get('ID_BROKER_ASSERT_VALID_IP'), + 'idBrokerBaseUri' => Env::get('ID_BROKER_BASE_URI'), + 'idBrokerClientClass' => FakeIdBrokerClient::class, + 'idBrokerTrustedIpRanges' => Env::get('ID_BROKER_TRUSTED_IP_RANGES'), + 'idpDomainName' => Env::get('IDP_DOMAIN_NAME'), + 'mfaSetupUrl' => Env::get('MFA_SETUP_URL'), + 'loggerClass' => Psr3SamlLogger::class, + ], + 15 => [ 'class' => 'expirychecker:ExpiryDate', 'accountNameAttr' => 'cn', 'expiryDateAttr' => 'schacExpiryDate', @@ -37,8 +54,18 @@ 'dateFormat' => 'Y-m-d', 'loggerClass' => Psr3StdOutLogger::class, ], + 30 => [ + 'class' => 'profilereview:ProfileReview', + 'employeeIdAttr' => 'employeeNumber', + 'mfaLearnMoreUrl' => Env::get('MFA_LEARN_MORE_URL'), + 'profileUrl' => Env::get('PROFILE_URL'), + 'loggerClass' => Psr3SamlLogger::class, + ], ], ]; -// Duplicate configuration for port 80. +// Copy configuration for port 80 and modify host and profileUrl. $metadata['http://ssp-idp1.local'] = $metadata['http://ssp-idp1.local:8085']; +$metadata['http://ssp-idp1.local']['host'] = 'ssp-idp1.local'; +$metadata['http://ssp-idp1.local']['authproc'][10]['mfaSetupUrl'] = Env::get('PROFILE_URL_FOR_TESTS'); +$metadata['http://ssp-idp1.local']['authproc'][30]['profileUrl'] = Env::get('PROFILE_URL_FOR_TESTS'); diff --git a/development/idp2-local/config/authsources.php b/development/idp2-local/config/authsources.php index a5d7d017..197f61b1 100644 --- a/development/idp2-local/config/authsources.php +++ b/development/idp2-local/config/authsources.php @@ -10,4 +10,30 @@ 'core:AdminPassword', ], + 'example-userpass' => [ + 'exampleauth:UserPass', + + // sildisco test user + 'sildisco_idp2:sildisco_password' => [ + 'eduPersonPrincipalName' => ['sildisco@idp2'], + 'eduPersonTargetID' => ['57de2930-c5d2-4f6f-9328-d85a939c45d8'], + 'sn' => ['IDP2'], + 'givenName' => ['SilDisco'], + 'mail' => ['sildisco_idp2@example.com'], + 'employeeNumber' => ['50002'], + 'cn' => ['SILDISCO_IDP2'], + 'schacExpiryDate' => [ + gmdate('YmdHis\Z', strtotime('+6 months')), + ], + 'mfa' => [ + 'prompt' => 'no', + 'add' => 'no', + 'options' => [], + ], + 'method' => [ + 'add' => 'no', + ], + 'profile_review' => 'no' + ], + ] ]; diff --git a/development/idp2-local/config/config.php b/development/idp2-local/config/config.php index ac0501e0..db650c58 100644 --- a/development/idp2-local/config/config.php +++ b/development/idp2-local/config/config.php @@ -2,10 +2,15 @@ /* * The configuration of SimpleSAMLphp * + * 2020-04-17 -- Updated to simplesamlphp/config-templates/config.php 1.18.6 + * */ use Sil\PhpEnv\Env; use Sil\PhpEnv\EnvVarNotFoundException; -use Sil\SspUtils\AnnouncementUtils; + +/* + * Get config settings from ENV vars or set defaults + */ $logLevels = [ 'ERR' => SimpleSAML\Logger::ERR, // No statistics, only errors @@ -15,16 +20,13 @@ 'DEBUG' => SimpleSAML\Logger::DEBUG, // Full debug logs - not recommended for production ]; -/* - * Get config settings from ENV vars or set defaults - */ - try { // Required to be defined in environment variables $ADMIN_EMAIL = Env::requireEnv('ADMIN_EMAIL'); $ADMIN_PASS = Env::requireEnv('ADMIN_PASS'); $SECRET_SALT = Env::requireEnv('SECRET_SALT'); $IDP_NAME = Env::requireEnv('IDP_NAME'); + $IDP_DISPLAY_NAME = Env::get('IDP_DISPLAY_NAME', $IDP_NAME); } catch (EnvVarNotFoundException $e) { // Return error response code/message to HTTP request. @@ -49,57 +51,267 @@ $ENABLE_DEBUG = Env::get('ENABLE_DEBUG', false); $LOGGING_LEVEL = Env::get('LOGGING_LEVEL', 'NOTICE'); $LOGGING_HANDLER = Env::get('LOGGING_HANDLER', 'stderr'); -$SESSION_DURATION = (int)(Env::get('SESSION_DURATION', 540)); -$SESSION_DATASTORE_TIMEOUT = (int)(Env::get('SESSION_DATASTORE_TIMEOUT', (4 * 60 * 60))); // 4 hours -$SESSION_STATE_TIMEOUT = (int)(Env::get('SESSION_STATE_TIMEOUT', (60 * 60))); // 1 hour -$SESSION_COOKIE_LIFETIME = (int)(Env::get('SESSION_COOKIE_LIFETIME', 0)); -$SESSION_REMEMBERME_LIFETIME = (int)(Env::get('SESSION_REMEMBERME_LIFETIME', (14 * 86400))); // 14 days +$THEME_USE = Env::get('THEME_USE', 'material:material'); + +// Options: https://github.com/silinternational/simplesamlphp-module-material/blob/develop/README.md#branding +$THEME_COLOR_SCHEME = Env::get('THEME_COLOR_SCHEME', null); + $SECURE_COOKIE = Env::get('SECURE_COOKIE', true); -$THEME_USE = Env::get('THEME_USE', 'default'); +$SESSION_DURATION = (int)(Env::get('SESSION_DURATION', (60 * 60 * 10))); // 10 hours. +$SESSION_STORE_TYPE = Env::get('SESSION_STORE_TYPE', 'phpsession'); +$MEMCACHE_HOST1 = Env::get('MEMCACHE_HOST1', null); +$MEMCACHE_HOST2 = Env::get('MEMCACHE_HOST2', null); +$MEMCACHE_HOST1_PORT = Env::get('MEMCACHE_HOST1_PORT', 11211); +$MEMCACHE_HOST2_PORT = Env::get('MEMCACHE_HOST2_PORT', 11211); +$MYSQL_HOST = Env::get('MYSQL_HOST', ''); +$MYSQL_DATABASE = Env::get('MYSQL_DATABASE', ''); +$MYSQL_USER = Env::get('MYSQL_USER', ''); +$MYSQL_PASSWORD = Env::get('MYSQL_PASSWORD', ''); + $SAML20_IDP_ENABLE = Env::get('SAML20_IDP_ENABLE', true); $GOOGLE_ENABLE = Env::get('GOOGLE_ENABLE', false); +$HUB_MODE = Env::get('HUB_MODE', false); +$ANALYTICS_ID = Env::get('ANALYTICS_ID', null); +$PASSWORD_CHANGE_URL = Env::get('PASSWORD_CHANGE_URL'); +$PASSWORD_FORGOT_URL = Env::get('PASSWORD_FORGOT_URL'); +$HELP_CENTER_URL = Env::get('HELP_CENTER_URL'); $config = [ /* - * Get a string of html to show as an announcement on the discovery page - * and/or login page. By default, this will be fetched from - * .../vendor/simplesamlphp/simplesamlphp/announcement/announcement.php + * Whether this instance should act as a hub/proxy/bridge using sildisco + */ + 'hubmode' => $HUB_MODE, + + /* + * Name of this IdP + */ + 'idp_name' => $IDP_NAME, + + /* + * Name of this IdP to display to the user + */ + 'idp_display_name' => $IDP_DISPLAY_NAME, + + /* + * The tracking Id for Google Analytics or some other similar service */ - 'announcement' => AnnouncementUtils::getAnnouncement(), + 'analytics.trackingId' => $ANALYTICS_ID, + + 'passwordChangeUrl' => $PASSWORD_CHANGE_URL, + 'passwordForgotUrl' => $PASSWORD_FORGOT_URL, + 'helpCenterUrl' => $HELP_CENTER_URL, + + /******************************* + | BASIC CONFIGURATION OPTIONS | + *******************************/ /* - * Setup the following parameters to match the directory of your installation. + * Setup the following parameters to match your installation. * See the user manual for more details. - * - * Valid format for baseurlpath is: + */ + + /* + * baseurlpath is a *URL path* (not a filesystem path). + * A valid format for 'baseurlpath' is: * [(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/] - * (note that it must end with a '/') * - * The full url format is useful if your simpleSAMLphp setup is hosted behind + * The full url format is useful if your SimpleSAMLphp setup is hosted behind * a reverse proxy. In that case you can specify the external url here. * - * Please note that simpleSAMLphp will then redirect all queries to the + * Please note that SimpleSAMLphp will then redirect all queries to the * external url, no matter where you come from (direct access or via the * reverse proxy). */ 'baseurlpath' => $BASE_URL_PATH, + + /* + * The 'application' configuration array groups a set configuration options + * relative to an application protected by SimpleSAMLphp. + */ + //'application' => [ + /* + * The 'baseURL' configuration option allows you to specify a protocol, + * host and optionally a port that serves as the canonical base for all + * your application's URLs. This is useful when the environment + * observed in the server differs from the one observed by end users, + * for example, when using a load balancer to offload TLS. + * + * Note that this configuration option does not allow setting a path as + * part of the URL. If your setup involves URL rewriting or any other + * tricks that would result in SimpleSAMLphp observing a URL for your + * application's scripts different than the canonical one, you will + * need to compute the right URLs yourself and pass them dynamically + * to SimpleSAMLphp's API. + */ + //'baseURL' => 'https://example.com', + //], + + /* + * The following settings are *filesystem paths* which define where + * SimpleSAMLphp can find or write the following things: + * - 'certdir': The base directory for certificate and key material. + * - 'loggingdir': Where to write logs. + * - 'datadir': Storage of general data. + * - 'tempdir': Saving temporary files. SimpleSAMLphp will attempt to create + * this directory if it doesn't exist. + * When specified as a relative path, this is relative to the SimpleSAMLphp + * root directory. + */ 'certdir' => 'cert/', 'loggingdir' => 'log/', 'datadir' => 'data/', + 'tempdir' => '/tmp/simplesaml', + + /* + * Some information about the technical persons running this installation. + * The email address will be used as the recipient address for error reports, and + * also as the technical contact in generated metadata. + */ + 'technicalcontact_name' => $ADMIN_NAME, + 'technicalcontact_email' => $ADMIN_EMAIL, /* - * A directory where simpleSAMLphp can save temporary files. + * (Optional) The method by which email is delivered. Defaults to mail which utilizes the + * PHP mail() function. * - * SimpleSAMLphp will attempt to create this directory if it doesn't exist. + * Valid options are: mail, sendmail and smtp. */ - 'tempdir' => '/tmp/simplesaml', + //'mail.transport.method' => 'smtp', /* - * Name of this IdP to display to the user + * Set the transport options for the transport method specified. The valid settings are relative to the + * selected transport method. */ - 'idp_name' => $IDP_NAME, + // // smtp mail transport options + // 'mail.transport.options' => [ + // 'host' => 'mail.example.org', // required + // 'port' => 25, // optional + // 'username' => 'user@example.org', // optional: if set, enables smtp authentication + // 'password' => 'password', // optional: if set, enables smtp authentication + // 'security' => 'tls', // optional: defaults to no smtp security + // ], + // // sendmail mail transport options + // 'mail.transport.options' => [ + // 'path' => '/usr/sbin/sendmail' // optional: defaults to php.ini path + // ], + + /* + * The envelope from address for outgoing emails. + * This should be in a domain that has your application's IP addresses in its SPF record + * to prevent it from being rejected by mail filters. + */ + //'sendmail_from' => 'no-reply@example.org', + + /* + * The timezone of the server. This option should be set to the timezone you want + * SimpleSAMLphp to report the time in. The default is to guess the timezone based + * on your system timezone. + * + * See this page for a list of valid timezones: http://php.net/manual/en/timezones.php + */ + 'timezone' => $TIMEZONE, + + + + /********************************** + | SECURITY CONFIGURATION OPTIONS | + **********************************/ + + /* + * This is a secret salt used by SimpleSAMLphp when it needs to generate a secure hash + * of a value. It must be changed from its default value to a secret value. The value of + * 'secretsalt' can be any valid string of any length. + * + * A possible way to generate a random salt is by running the following command from a unix shell: + * LC_CTYPE=C tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' /dev/null;echo + */ + 'secretsalt' => $SECRET_SALT, + + /* + * This password must be kept secret, and modified from the default value 123. + * This password will give access to the installation page of SimpleSAMLphp with + * metadata listing and diagnostics pages. + * You can also put a hash here; run "bin/pwgen.php" to generate one. + */ + 'auth.adminpassword' => $ADMIN_PASS, + + /* + * Set this options to true if you want to require administrator password to access the web interface + * or the metadata pages, respectively. + */ + 'admin.protectindexpage' => $ADMIN_PROTECT_INDEX_PAGE, + 'admin.protectmetadata' => true, + + /* + * Set this option to false if you don't want SimpleSAMLphp to check for new stable releases when + * visiting the configuration tab in the web interface. + */ + 'admin.checkforupdates' => false, + + /* + * Array of domains that are allowed when generating links or redirects + * to URLs. SimpleSAMLphp will use this option to determine whether to + * to consider a given URL valid or not, but you should always validate + * URLs obtained from the input on your own (i.e. ReturnTo or RelayState + * parameters obtained from the $_REQUEST array). + * + * SimpleSAMLphp will automatically add your own domain (either by checking + * it dynamically, or by using the domain defined in the 'baseurlpath' + * directive, the latter having precedence) to the list of trusted domains, + * in case this option is NOT set to NULL. In that case, you are explicitly + * telling SimpleSAMLphp to verify URLs. + * + * Set to an empty array to disallow ALL redirects or links pointing to + * an external URL other than your own domain. This is the default behaviour. + * + * Set to NULL to disable checking of URLs. DO NOT DO THIS UNLESS YOU KNOW + * WHAT YOU ARE DOING! + * + * Example: + * 'trusted.url.domains' => ['sp.example.com', 'app.example.com'], + */ + 'trusted.url.domains' => null, + + /* + * Enable regular expression matching of trusted.url.domains. + * + * Set to true to treat the values in trusted.url.domains as regular + * expressions. Set to false to do exact string matching. + * + * If enabled, the start and end delimiters ('^' and '$') will be added to + * all regular expressions in trusted.url.domains. + */ + 'trusted.url.regex' => false, + + /* + * Enable secure POST from HTTPS to HTTP. + * + * If you have some SP's on HTTP and IdP is normally on HTTPS, this option + * enables secure POSTing to HTTP endpoint without warning from browser. + * + * For this to work, module.php/core/postredirect.php must be accessible + * also via HTTP on IdP, e.g. if your IdP is on + * https://idp.example.org/ssp/, then + * http://idp.example.org/ssp/module.php/core/postredirect.php must be accessible. + */ + 'enable.http_post' => false, + + /* + * Set the allowed clock skew between encrypting/decrypting assertions + * + * If you have an server that is constantly out of sync, this option + * allows you to adjust the allowed clock-skew. + * + * Allowed range: 180 - 300 + * Defaults to 180. + */ + 'assertion.allowed_clock_skew' => 180, + + /************************ + | ERRORS AND DEBUGGING | + ************************/ /* * The 'debug' option allows you to control how SimpleSAMLphp behaves in certain @@ -140,75 +352,36 @@ ], /* - * When showerrors is enabled, all error messages and stack traces will be output + * When 'showerrors' is enabled, all error messages and stack traces will be output * to the browser. * - * When errorreporting is enabled, a form will be presented for the user to report - * the error to technicalcontact_email. + * When 'errorreporting' is enabled, a form will be presented for the user to report + * the error to 'technicalcontact_email'. */ 'showerrors' => $SHOW_SAML_ERRORS, 'errorreporting' => false, /* - * Custom error show function called from SimpleSAML_Error_Error::show. + * Custom error show function called from SimpleSAML\Error\Error::show. * See docs/simplesamlphp-errorhandling.txt for function code example. * * Example: - * 'errors.show_function' => array('sspmod_example_Error_Show', 'show'), - */ - - /* - * This option allows you to enable validation of XML data against its - * schemas. A warning will be written to the log if validation fails. - */ - 'debug.validatexml' => false, - - /* - * This password must be kept secret, and modified from the default value 123. - * This password will give access to the installation page of simpleSAMLphp with - * metadata listing and diagnostics pages. - * You can also put a hash here; run "bin/pwgen.php" to generate one. + * 'errors.show_function' => ['SimpleSAML\Module\example\Error', 'show'], */ - 'auth.adminpassword' => $ADMIN_PASS, - 'admin.protectindexpage' => $ADMIN_PROTECT_INDEX_PAGE, - 'admin.protectmetadata' => true, - /* - * This is a secret salt used by simpleSAMLphp when it needs to generate a secure hash - * of a value. It must be changed from its default value to a secret value. The value of - * 'secretsalt' can be any valid string of any length. - * - * A possible way to generate a random salt is by running the following command from a unix shell: - * tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' /dev/null;echo - */ - 'secretsalt' => $SECRET_SALT, - /* - * Some information about the technical persons running this installation. - * The email address will be used as the recipient address for error reports, and - * also as the technical contact in generated metadata. - */ - 'technicalcontact_name' => $ADMIN_NAME, - 'technicalcontact_email' => $ADMIN_EMAIL, - /* - * The timezone of the server. This option should be set to the timezone you want - * simpleSAMLphp to report the time in. The default is to guess the timezone based - * on your system timezone. - * - * See this page for a list of valid timezones: http://php.net/manual/en/timezones.php - */ - 'timezone' => $TIMEZONE, + /************************** + | LOGGING AND STATISTICS | + **************************/ /* - * Logging. - * - * define the minimum log level to log - * SimpleSAML\Logger::ERR No statistics, only errors - * SimpleSAML\Logger::WARNING No statistics, only warnings/errors - * SimpleSAML\Logger::NOTICE Statistics and errors - * SimpleSAML\Logger::INFO Verbose logs - * SimpleSAML\Logger::DEBUG Full debug logs - not reccomended for production + * Define the minimum log level to log. Available levels: + * - SimpleSAML\Logger::ERR No statistics, only errors + * - SimpleSAML\Logger::WARNING No statistics, only warnings/errors + * - SimpleSAML\Logger::NOTICE Statistics and errors + * - SimpleSAML\Logger::INFO Verbose logs + * - SimpleSAML\Logger::DEBUG Full debug logs - not recommended for production * * Choose logging handler. * @@ -249,7 +422,7 @@ /* * Choose which facility should be used when logging with syslog. * - * These can be used for filtering the syslog output from simpleSAMLphp into its + * These can be used for filtering the syslog output from SimpleSAMLphp into its * own file by configuring the syslog daemon. * * See the documentation for openlog (http://php.net/manual/en/function.openlog.php) for available @@ -265,12 +438,12 @@ */ 'logging.processname' => 'simplesamlphp', - /* Logging: file - Logfilename in the loggingdir from above. + /* + * Logging: file - Logfilename in the loggingdir from above. */ 'logging.logfile' => 'simplesamlphp.log', - /* (New) statistics output configuration. - * + /* * This is an array of outputs. Each output has at least a 'class' option, which * selects the output. */ @@ -291,35 +464,129 @@ ], + + /*********************** + | PROXY CONFIGURATION | + ***********************/ + /* - * Enable + * Proxy to use for retrieving URLs. * - * Which functionality in simpleSAMLphp do you want to enable. Normally you would enable only + * Example: + * 'proxy' => 'tcp://proxy.example.com:5100' + */ + 'proxy' => null, + + /* + * Username/password authentication to proxy (Proxy-Authorization: Basic) + * Example: + * 'proxy.auth' = 'myuser:password' + */ + //'proxy.auth' => 'myuser:password', + + + + /************************** + | DATABASE CONFIGURATION | + **************************/ + + /* + * This database configuration is optional. If you are not using + * core functionality or modules that require a database, you can + * skip this configuration. + */ + + /* + * Database connection string. + * Ensure that you have the required PDO database driver installed + * for your connection string. + */ + //'database.dsn' => 'mysql:host=localhost;dbname=saml', + + /* + * SQL database credentials + */ + //'database.username' => 'simplesamlphp', + //'database.password' => 'secret', + //'database.options' => [], + + /* + * (Optional) Table prefix + */ + //'database.prefix' => '', + + /* + * (Optional) Driver options + */ + //'database.driver_options' => [], + + /* + * True or false if you would like a persistent database connection + */ + //'database.persistent' => false, + + /* + * Database slave configuration is optional as well. If you are only + * running a single database server, leave this blank. If you have + * a master/slave configuration, you can define as many slave servers + * as you want here. Slaves will be picked at random to be queried from. + * + * Configuration options in the slave array are exactly the same as the + * options for the master (shown above) with the exception of the table + * prefix and driver options. + */ + //'database.slaves' => [ + // /* + // [ + // 'dsn' => 'mysql:host=myslave;dbname=saml', + // 'username' => 'simplesamlphp', + // 'password' => 'secret', + // 'persistent' => false, + // ], + // */ + //], + + + + /************* + | PROTOCOLS | + *************/ + + /* + * Which functionality in SimpleSAMLphp do you want to enable. Normally you would enable only * one of the functionalities below, but in some cases you could run multiple functionalities. * In example when you are setting up a federation bridge. */ 'enable.saml20-idp' => $SAML20_IDP_ENABLE, 'enable.shib13-idp' => false, 'enable.adfs-idp' => false, - 'enable.wsfed-sp' => false, - 'enable.authmemcookie' => false, - /* - * Module enable configuration + * Whether SimpleSAMLphp should sign the response or the assertion in SAML 1.1 authentication + * responses. * + * The default is to sign the assertion element, but that can be overridden by setting this + * option to TRUE. It can also be overridden on a pr. SP basis by adding an option with the + * same name to the metadata of the SP. + */ + 'shib13.signresponse' => true, + + + + /*********** + | MODULES | + ***********/ + + /* * Configuration to override module enabling/disabling. * * Example: * - * 'module.enable' => array( - * // Setting to TRUE enables. - * 'exampleauth' => TRUE, - * // Setting to FALSE disables. - * 'saml' => FALSE, - * // Unset or NULL uses default. - * 'core' => NULL, - * ), + * 'module.enable' => [ + * 'exampleauth' => true, // Setting to TRUE enables. + * 'consent' => false, // Setting to FALSE disables. + * 'core' => null, // Unset or NULL uses default. + * ], * */ @@ -332,8 +599,15 @@ 'profilereview' => true, 'silauth' => true, 'sildisco' => true, + 'exampleauth' => true, ], + + + /************************* + | SESSION CONFIGURATION | + *************************/ + /* * This value is the duration of the session in seconds. Make sure that the time duration of * cookies both at the SP and the IdP exceeds this duration. @@ -341,16 +615,16 @@ 'session.duration' => $SESSION_DURATION, /* - * Sets the duration, in seconds, data should be stored in the datastore. As the datastore is used for - * login and logout requests, thid option will control the maximum time these operations can take. + * Sets the duration, in seconds, data should be stored in the datastore. As the data store is used for + * login and logout requests, this option will control the maximum time these operations can take. * The default is 4 hours (4*60*60) seconds, which should be more than enough for these operations. */ - 'session.datastore.timeout' => $SESSION_DATASTORE_TIMEOUT, + 'session.datastore.timeout' => $SESSION_DURATION, /* * Sets the duration, in seconds, auth state should be stored. */ - 'session.state.timeout' => $SESSION_STATE_TIMEOUT, + 'session.state.timeout' => $SESSION_DURATION, /* * Option to override the default settings for the session cookie name @@ -365,7 +639,7 @@ * Example: * 'session.cookie.lifetime' => 30*60, */ - 'session.cookie.lifetime' => $SESSION_COOKIE_LIFETIME, + 'session.cookie.lifetime' => 0, /* * Limit the path of the cookies. @@ -397,28 +671,21 @@ 'session.cookie.secure' => $SECURE_COOKIE, /* - * When set to FALSE fallback to transient session on session initialization - * failure, throw exception otherwise. - */ - 'session.disable_fallback' => false, - - /* - * Enable secure POST from HTTPS to HTTP. + * Set the SameSite attribute in the cookie. * - * If you have some SP's on HTTP and IdP is normally on HTTPS, this option - * enables secure POSTing to HTTP endpoint without warning from browser. + * You can set this to the strings 'None', 'Lax', or 'Strict' to support + * the RFC6265bis SameSite cookie attribute. If set to null, no SameSite + * attribute will be sent. * - * For this to work, module.php/core/postredirect.php must be accessible - * also via HTTP on IdP, e.g. if your IdP is on - * https://idp.example.org/ssp/, then - * http://idp.example.org/ssp/module.php/core/postredirect.php must be accessible. + * Example: + * 'session.cookie.samesite' => 'None', */ - 'enable.http_post' => false, + 'session.cookie.samesite' => null, /* * Options to override the default settings for php sessions. */ - 'session.phpsession.cookiename' => null, + 'session.phpsession.cookiename' => 'SimpleSAML', 'session.phpsession.savepath' => null, 'session.phpsession.httponly' => true, @@ -442,23 +709,195 @@ */ 'session.rememberme.enable' => false, 'session.rememberme.checked' => false, - 'session.rememberme.lifetime' => $SESSION_REMEMBERME_LIFETIME, + 'session.rememberme.lifetime' => (14 * 86400), // 14 days - /** + /* * Custom function for session checking called on session init and loading. * See docs/simplesamlphp-advancedfeatures.txt for function code example. * * Example: - * 'session.check_function' => array('sspmod_example_Util', 'checkSession'), + * 'session.check_function' => ['\SimpleSAML\Module\example\Util', 'checkSession'], */ + + + /************************** + | MEMCACHE CONFIGURATION | + **************************/ + /* - * Languages available, RTL languages, and what language is default + * Configuration for the 'memcache' session store. This allows you to store + * multiple redundant copies of sessions on different memcache servers. + * + * 'memcache_store.servers' is an array of server groups. Every data + * item will be mirrored in every server group. + * + * Each server group is an array of servers. The data items will be + * load-balanced between all servers in each server group. + * + * Each server is an array of parameters for the server. The following + * options are available: + * - 'hostname': This is the hostname or ip address where the + * memcache server runs. This is the only required option. + * - 'port': This is the port number of the memcache server. If this + * option isn't set, then we will use the 'memcache.default_port' + * ini setting. This is 11211 by default. + * + * When using the "memcache" extension, the following options are also + * supported: + * - 'weight': This sets the weight of this server in this server + * group. http://php.net/manual/en/function.Memcache-addServer.php + * contains more information about the weight option. + * - 'timeout': The timeout for this server. By default, the timeout + * is 3 seconds. + * + * Example of redundant configuration with load balancing: + * This configuration makes it possible to lose both servers in the + * a-group or both servers in the b-group without losing any sessions. + * Note that sessions will be lost if one server is lost from both the + * a-group and the b-group. + * + * 'memcache_store.servers' => [ + * [ + * ['hostname' => 'mc_a1'], + * ['hostname' => 'mc_a2'], + * ], + * [ + * ['hostname' => 'mc_b1'], + * ['hostname' => 'mc_b2'], + * ], + * ], + * + * Example of simple configuration with only one memcache server, + * running on the same computer as the web server: + * Note that all sessions will be lost if the memcache server crashes. + * + * 'memcache_store.servers' => [ + * [ + * ['hostname' => 'localhost'], + * ], + * ], + * + * Additionally, when using the "memcached" extension, unique keys must + * be provided for each group of servers if persistent connections are + * desired. Each server group can also have an "options" indexed array + * with the options desired for the given group: + * + * 'memcache_store.servers' => [ + * 'memcache_group_1' => [ + * 'options' => [ + * \Memcached::OPT_BINARY_PROTOCOL => true, + * \Memcached::OPT_NO_BLOCK => true, + * \Memcached::OPT_TCP_NODELAY => true, + * \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, + * ], + * ['hostname' => '127.0.0.1', 'port' => 11211], + * ['hostname' => '127.0.0.2', 'port' => 11211], + * ], + * + * 'memcache_group_2' => [ + * 'options' => [ + * \Memcached::OPT_BINARY_PROTOCOL => true, + * \Memcached::OPT_NO_BLOCK => true, + * \Memcached::OPT_TCP_NODELAY => true, + * \Memcached::OPT_LIBKETAMA_COMPATIBLE => true, + * ], + * ['hostname' => '127.0.0.3', 'port' => 11211], + * ['hostname' => '127.0.0.4', 'port' => 11211], + * ], + * ], + * + */ + 'memcache_store.servers' => [ + [ + [ + 'hostname' => $MEMCACHE_HOST1, + 'port' => $MEMCACHE_HOST1_PORT, + ], + ], + [ + [ + 'hostname' => $MEMCACHE_HOST2, + 'port' => $MEMCACHE_HOST2_PORT, + ], + ], + ], + + /* + * This value allows you to set a prefix for memcache-keys. The default + * for this value is 'simpleSAMLphp', which is fine in most cases. + * + * When running multiple instances of SSP on the same host, and more + * than one instance is using memcache, you probably want to assign + * a unique value per instance to this setting to avoid data collision. + */ + //'memcache_store.prefix' => '', + + /* + * This value is the duration data should be stored in memcache. Data + * will be dropped from the memcache servers when this time expires. + * The time will be reset every time the data is written to the + * memcache servers. + * + * This value should always be larger than the 'session.duration' + * option. Not doing this may result in the session being deleted from + * the memcache servers while it is still in use. + * + * Set this value to 0 if you don't want data to expire. + * + * Note: The oldest data will always be deleted if the memcache server + * runs out of storage space. + */ + 'memcache_store.expires' => $SESSION_DURATION + 3600, // Session duration plus an hour for clock skew + + + + /************************************* + | LANGUAGE AND INTERNATIONALIZATION | + *************************************/ + + /* + * Language-related options. + */ + 'language' => [ + /* + * An array in the form 'language' => . + * + * Each key in the array is the ISO 639 two-letter code for a language, + * and its value is an array with a list of alternative languages that + * can be used if the given language is not available at some point. + * Each alternative language is also specified by its ISO 639 code. + * + * For example, for the "no" language code (Norwegian), we would have: + * + * 'priorities' => [ + * 'no' => ['nb', 'nn', 'en', 'se'], + * ... + * ], + * + * establishing that if a translation for the "no" language code is + * not available, we look for translations in "nb" (Norwegian Bokmål), + * and so on, in that order. + */ + 'priorities' => [ + 'no' => ['nb', 'nn', 'en', 'se'], + 'nb' => ['no', 'nn', 'en', 'se'], + 'nn' => ['no', 'nb', 'en', 'se'], + 'se' => ['nb', 'no', 'nn', 'en'], + 'nr' => ['zu', 'en'], + 'nd' => ['zu', 'en'], + ], + ], + + /* + * Languages available, RTL languages, and what language is the default. */ - 'language.available' => array( - 'en', 'es', 'fr', 'pt', - ), - 'language.rtl' => array('ar', 'dv', 'fa', 'ur', 'he'), + 'language.available' => [ + 'en', 'no', 'nn', 'se', 'da', 'de', 'sv', 'fi', 'es', 'ca', 'fr', 'it', 'nl', 'lb', + 'cs', 'sl', 'lt', 'hr', 'hu', 'pl', 'pt', 'pt-br', 'tr', 'ja', 'zh', 'zh-tw', 'ru', + 'et', 'he', 'id', 'sr', 'lv', 'ro', 'eu', 'el', 'af', 'zu', 'xh', + ], + 'language.rtl' => ['ar', 'dv', 'fa', 'ur', 'he'], 'language.default' => 'en', /* @@ -473,18 +912,21 @@ 'language.cookie.name' => 'language', 'language.cookie.domain' => null, 'language.cookie.path' => '/', + 'language.cookie.secure' => false, + 'language.cookie.httponly' => false, 'language.cookie.lifetime' => (60 * 60 * 24 * 900), + 'language.cookie.samesite' => null, /** - * Custom getLanguage function called from SimpleSAML_XHTML_Template::getLanguage(). + * Custom getLanguage function called from SimpleSAML\Locale\Language::getLanguage(). * Function should return language code of one of the available languages or NULL. - * See SimpleSAML_XHTML_Template::getLanguage() source code for more info. + * See SimpleSAML\Locale\Language::getLanguage() source code for more info. * * This option can be used to implement a custom function for determining * the default language for the user. * * Example: - * 'language.get_language_function' => array('sspmod_example_Template', 'getLanguage'), + * 'language.get_language_function' => ['\SimpleSAML\Module\example\Template', 'getLanguage'], */ /* @@ -513,16 +955,98 @@ */ 'attributes.extradictionary' => null, + + + /************** + | APPEARANCE | + **************/ + /* * Which theme directory should be used? */ 'theme.use' => $THEME_USE, + /* + * Set this option to the text you would like to appear at the header of each page. Set to false if you don't want + * any text to appear in the header. + */ + //'theme.header' => 'SimpleSAMLphp' + + /** + * A template controller, if any. + * + * Used to intercept certain parts of the template handling, while keeping away unwanted/unexpected hooks. Set + * the 'theme.controller' configuration option to a class that implements the + * \SimpleSAML\XHTML\TemplateControllerInterface interface to use it. + */ + //'theme.controller' => '', + + /* + * Templating options + * + * By default, twig templates are not cached. To turn on template caching: + * Set 'template.cache' to an absolute path pointing to a directory that + * SimpleSAMLphp has read and write permissions to. + */ + //'template.cache' => '', + + /* + * Set the 'template.auto_reload' to true if you would like SimpleSAMLphp to + * recompile the templates (when using the template cache) if the templates + * change. If you don't want to check the source templates for every request, + * set it to false. + */ + 'template.auto_reload' => false, + + /* + * Set this option to true to indicate that your installation of SimpleSAMLphp + * is running in a production environment. This will affect the way resources + * are used, offering an optimized version when running in production, and an + * easy-to-debug one when not. Set it to false when you are testing or + * developing the software, in which case a banner will be displayed to remind + * users that they're dealing with a non-production instance. + * + * Defaults to true. + */ + 'production' => true, + + /* + * SimpleSAMLphp modules can host static resources which are served through PHP. + * The serving of the resources can be configured through these settings. + */ + 'assets' => [ + /* + * These settings adjust the caching headers that are sent + * when serving static resources. + */ + 'caching' => [ + /* + * Amount of seconds before the resource should be fetched again + */ + 'max_age' => 86400, + /* + * Calculate a checksum of every file and send it to the browser + * This allows the browser to avoid downloading assets again in situations + * where the Last-Modified header cannot be trusted, + * for example in cluster setups + * + * Defaults false + */ + 'etag' => false, + ], + ], /* - * Default IdP for WS-Fed. + * If using the material theme, which color scheme to use + * Options: https://github.com/silinternational/simplesamlphp-module-material/blob/develop/README.md#branding */ - // 'default-wsfed-idp' => 'urn:federation:pingfederate:localhost', + 'theme.color-scheme' => $THEME_COLOR_SCHEME, + + + + /********************* + | DISCOVERY SERVICE | + *********************/ /* * Whether the discovery service should allow the user to save his choice of IdP. @@ -530,7 +1054,9 @@ 'idpdisco.enableremember' => true, 'idpdisco.rememberchecked' => true, - // Disco service only accepts entities it knows. + /* + * The disco service only accepts entities it knows. + */ 'idpdisco.validate' => true, 'idpdisco.extDiscoveryStorage' => null, @@ -544,30 +1070,24 @@ * This makes it easier for the user to choose the IdP * * Options: [links,dropdown] - * */ 'idpdisco.layout' => 'links', - /* - * Whether simpleSAMLphp should sign the response or the assertion in SAML 1.1 authentication - * responses. - * - * The default is to sign the assertion element, but that can be overridden by setting this - * option to TRUE. It can also be overridden on a pr. SP basis by adding an option with the - * same name to the metadata of the SP. - */ - 'shib13.signresponse' => true, + /************************************* + | AUTHENTICATION PROCESSING FILTERS | + *************************************/ /* * Authentication processing filters that will be executed for all IdPs * Both Shibboleth and SAML 2.0 */ 'authproc.idp' => [ - /* Enable the authproc filter below to add URN Prefixces to all attributes - 10 => array( - 'class' => 'core:AttributeMap', 'addurnprefix' - ), */ + /* Enable the authproc filter below to add URN prefixes to all attributes + 10 => [ + 'class' => 'core:AttributeMap', 'addurnprefix' + ], + */ /* Enable the authproc filter below to automatically generated eduPersonTargetedID. 20 => 'core:TargetedID', */ @@ -575,89 +1095,133 @@ // Adopts language from attribute to use in UI 30 => 'core:LanguageAdaptor', - /* Add a realm attribute from edupersonprincipalname - 40 => 'core:AttributeRealm', - */ - 45 => [ + 35 => [ 'class' => 'core:StatisticsWithAttribute', 'attributename' => 'realm', 'type' => 'saml20-idp-SSO', ], + /* + * Copy friendly names attribute keys to oids ... + */ + 40 => [ + 'class' => 'core:AttributeMap', + 'name2oid', + '%duplicate', + ], + + /* + * Copy oid attribute keys to friendly names + */ + 41 => [ + 'class' => 'core:AttributeMap', + 'oid2name', + '%duplicate', + ], + + // 48 => *** WARNING: For Hubs this entry is added at the end of this file + + // If no attributes are requested in the SP metadata, then these will be sent through 50 => [ 'class' => 'core:AttributeLimit', 'default' => true, - 'eduPersonPrincipalName', 'sn', 'givenName', 'mail', + 'cn', + 'eduPersonPrincipalName', + 'eduPersonTargetID', + 'sn', + 'givenName', + 'mail', + 'employeeNumber', + 'urn:oid:2.5.4.3', // cn + 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6', // eduPersonPrincipalName + 'urn:oid:1.3.6.1.4.1.5923.1.1.1.10', // eduPersonTargetID + 'urn:oid:2.5.4.4', // sn + 'urn:oid:2.5.4.42', // givenName + 'urn:oid:0.9.2342.19200300.100.1.3', // mail + 'urn:oid:2.16.840.1.113730.3.1.3', // employeeNumber ], // Use the uid value to populate the nameid entry - 60 => [ - 'class' => 'saml:AttributeNameID', - 'attribute' => 'uid', - 'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', - ], + // 60 => [ + // 'class' => 'saml:AttributeNameID', + // 'attribute' => 'uid', + // 'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', + // ], /* * Search attribute "distinguishedName" for pattern and replaces if found - - 70 => array( + */ + /* + 70 => [ 'class' => 'core:AttributeAlter', 'pattern' => '/OU=studerende/', 'replacement' => 'Student', 'subject' => 'distinguishedName', '%replace', - ), - */ + ], + */ /* * Consent module is enabled (with no permanent storage, using cookies). - - 90 => array( + */ + /* + 90 => [ 'class' => 'consent:Consent', 'store' => 'consent:Cookie', 'focus' => 'yes', - 'checked' => TRUE - ), - */ - - + 'checked' => true + ], + */ // If language is set in Consent module it will be added as an attribute. 99 => 'core:LanguageAdaptor', ], + /* * Authentication processing filters that will be executed for all SPs * Both Shibboleth and SAML 2.0 */ 'authproc.sp' => [ /* - 10 => array( + 10 => [ 'class' => 'core:AttributeMap', 'removeurnprefix' - ), + ], */ /* * Generate the 'group' attribute populated from other variables, including eduPersonAffiliation. - 60 => array( + 60 => [ 'class' => 'core:GenerateGroups', 'eduPersonAffiliation' - ), + ], */ /* * All users will be members of 'users' and 'members' - 61 => array( - 'class' => 'core:AttributeAdd', 'groups' => array('users', 'members') - ), + */ + /* + 61 => [ + 'class' => 'core:AttributeAdd', 'groups' => ['users', 'members'] + ], */ // Adopts language from attribute to use in UI 90 => 'core:LanguageAdaptor', - ], + + /************************** + | METADATA CONFIGURATION | + **************************/ + + /* + * This option allows you to specify a directory for your metadata outside of the standard metadata directory + * included in the standard distribution of the software. + */ + //'metadatadir' => 'metadata', + /* * This option configures the metadata sources. The metadata sources is given as an array with - * different metadata sources. When searching for metadata, simpleSAMPphp will search through + * different metadata sources. When searching for metadata, SimpleSAMLphp will search through * the array from start to end. * * Each element in the array is an associative array which configures the metadata source. @@ -674,201 +1238,143 @@ * This metadata handler parses an XML file with either an EntityDescriptor element or an * EntitiesDescriptor element. The XML file may be stored locally, or (for debugging) on a remote * web server. - * The XML hetadata handler defines the following options: + * The XML metadata handler defines the following options: * - 'type': This is always 'xml'. * - 'file': Path to the XML file with the metadata. * - 'url': The URL to fetch metadata from. THIS IS ONLY FOR DEBUGGING - THERE IS NO CACHING OF THE RESPONSE. * + * MDQ metadata handler: + * This metadata handler looks up for the metadata of an entity at the given MDQ server. + * The MDQ metadata handler defines the following options: + * - 'type': This is always 'mdq'. + * - 'server': Base URL of the MDQ server. Mandatory. + * - 'validateFingerprint': The fingerprint of the certificate used to sign the metadata. You don't need this + * option if you don't want to validate the signature on the metadata. Optional. + * - 'cachedir': Directory where metadata can be cached. Optional. + * - 'cachelength': Maximum time metadata can be cached, in seconds. Defaults to 24 + * hours (86400 seconds). Optional. + * + * PDO metadata handler: + * This metadata handler looks up metadata of an entity stored in a database. + * + * Note: If you are using the PDO metadata handler, you must configure the database + * options in this configuration file. + * + * The PDO metadata handler defines the following options: + * - 'type': This is always 'pdo'. * * Examples: * * This example defines two flatfile sources. One is the default metadata directory, the other - * is a metadata directory with autogenerated metadata files. + * is a metadata directory with auto-generated metadata files. * - * 'metadata.sources' => array( - * array('type' => 'flatfile'), - * array('type' => 'flatfile', 'directory' => 'metadata-generated'), - * ), + * 'metadata.sources' => [ + * ['type' => 'flatfile'], + * ['type' => 'flatfile', 'directory' => 'metadata-generated'], + * ], * * This example defines a flatfile source and an XML source. - * 'metadata.sources' => array( - * array('type' => 'flatfile'), - * array('type' => 'xml', 'file' => 'idp.example.org-idpMeta.xml'), - * ), - * + * 'metadata.sources' => [ + * ['type' => 'flatfile'], + * ['type' => 'xml', 'file' => 'idp.example.org-idpMeta.xml'], + * ], + * + * This example defines an mdq source. + * 'metadata.sources' => [ + * [ + * 'type' => 'mdq', + * 'server' => 'http://mdq.server.com:8080', + * 'cachedir' => '/var/simplesamlphp/mdq-cache', + * 'cachelength' => 86400 + * ] + * ], + * + * This example defines an pdo source. + * 'metadata.sources' => [ + * ['type' => 'pdo'] + * ], * * Default: - * 'metadata.sources' => array( - * array('type' => 'flatfile') - * ), + * 'metadata.sources' => [ + * ['type' => 'flatfile'] + * ], */ 'metadata.sources' => [ ['type' => 'flatfile'], ], - /* - * Configure the datastore for simpleSAMLphp. - * - * - 'phpsession': Limited datastore, which uses the PHP session. - * - 'memcache': Key-value datastore, based on memcache. - * - 'sql': SQL datastore, using PDO. - * - * The default datastore is 'phpsession'. + * Should signing of generated metadata be enabled by default. * - * (This option replaces the old 'session.handler'-option.) + * Metadata signing can also be enabled for a individual SP or IdP by setting the + * same option in the metadata for the SP or IdP. */ - 'store.type' => 'phpsession', - + 'metadata.sign.enable' => true, /* - * The DSN the sql datastore should connect to. + * The default key & certificate which should be used to sign generated metadata. These + * are files stored in the cert dir. + * These values can be overridden by the options with the same names in the SP or + * IdP metadata. * - * See http://www.php.net/manual/en/pdo.drivers.php for the various - * syntaxes. - */ - 'store.sql.dsn' => 'sqlite:/path/to/sqlitedatabase.sq3', - - /* - * The username and password to use when connecting to the database. + * If these aren't specified here or in the metadata for the SP or IdP, then + * the 'certificate' and 'privatekey' option in the metadata will be used. + * if those aren't set, signing of metadata will fail. */ - 'store.sql.username' => null, - 'store.sql.password' => null, + 'metadata.sign.privatekey' => 'saml.pem', + 'metadata.sign.privatekey_pass' => null, + 'metadata.sign.certificate' => 'saml.crt', - /* - * The prefix we should use on our tables. - */ - 'store.sql.prefix' => 'simpleSAMLphp', + /**************************** + | DATA STORE CONFIGURATION | + ****************************/ /* - * Configuration for the MemcacheStore class. This allows you to store - * multiple redudant copies of sessions on different memcache servers. - * - * 'memcache_store.servers' is an array of server groups. Every data - * item will be mirrored in every server group. - * - * Each server group is an array of servers. The data items will be - * load-balanced between all servers in each server group. - * - * Each server is an array of parameters for the server. The following - * options are available: - * - 'hostname': This is the hostname or ip address where the - * memcache server runs. This is the only required option. - * - 'port': This is the port number of the memcache server. If this - * option isn't set, then we will use the 'memcache.default_port' - * ini setting. This is 11211 by default. - * - 'weight': This sets the weight of this server in this server - * group. http://php.net/manual/en/function.Memcache-addServer.php - * contains more information about the weight option. - * - 'timeout': The timeout for this server. By default, the timeout - * is 3 seconds. + * Configure the data store for SimpleSAMLphp. * - * Example of redudant configuration with load balancing: - * This configuration makes it possible to lose both servers in the - * a-group or both servers in the b-group without losing any sessions. - * Note that sessions will be lost if one server is lost from both the - * a-group and the b-group. - * - * 'memcache_store.servers' => array( - * array( - * array('hostname' => 'mc_a1'), - * array('hostname' => 'mc_a2'), - * ), - * array( - * array('hostname' => 'mc_b1'), - * array('hostname' => 'mc_b2'), - * ), - * ), - * - * Example of simple configuration with only one memcache server, - * running on the same computer as the web server: - * Note that all sessions will be lost if the memcache server crashes. - * - * 'memcache_store.servers' => array( - * array( - * array('hostname' => 'localhost'), - * ), - * ), + * - 'phpsession': Limited datastore, which uses the PHP session. + * - 'memcache': Key-value datastore, based on memcache. + * - 'sql': SQL datastore, using PDO. + * - 'redis': Key-value datastore, based on redis. * + * The default datastore is 'phpsession'. */ - 'memcache_store.servers' => [ - [ - ['hostname' => 'localhost'], - ], - ], - + 'store.type' => $SESSION_STORE_TYPE, /* - * This value is the duration data should be stored in memcache. Data - * will be dropped from the memcache servers when this time expires. - * The time will be reset every time the data is written to the - * memcache servers. - * - * This value should always be larger than the 'session.duration' - * option. Not doing this may result in the session being deleted from - * the memcache servers while it is still in use. - * - * Set this value to 0 if you don't want data to expire. + * The DSN the sql datastore should connect to. * - * Note: The oldest data will always be deleted if the memcache server - * runs out of storage space. + * See http://www.php.net/manual/en/pdo.drivers.php for the various + * syntaxes. */ - 'memcache_store.expires' => 36 * (60 * 60), // 36 hours. - + 'store.sql.dsn' => sprintf('mysql:host=%s;dbname=%s', $MYSQL_HOST, $MYSQL_DATABASE), /* - * Should signing of generated metadata be enabled by default. - * - * Metadata signing can also be enabled for a individual SP or IdP by setting the - * same option in the metadata for the SP or IdP. + * The username and password to use when connecting to the database. */ - 'metadata.sign.enable' => true, + 'store.sql.username' => $MYSQL_USER, + 'store.sql.password' => $MYSQL_PASSWORD, /* - * The default key & certificate which should be used to sign generated metadata. These - * are files stored in the cert dir. - * These values can be overridden by the options with the same names in the SP or - * IdP metadata. - * - * If these aren't specified here or in the metadata for the SP or IdP, then - * the 'certificate' and 'privatekey' option in the metadata will be used. - * if those aren't set, signing of metadata will fail. + * The prefix we should use on our tables. */ - 'metadata.sign.privatekey' => 'ssp-hub.pem', - 'metadata.sign.privatekey_pass' => null, - 'metadata.sign.certificate' => 'ssp-hub.crt', - + //'store.sql.prefix' => 'SimpleSAMLphp', /* - * Proxy to use for retrieving URLs. - * - * Example: - * 'proxy' => 'tcp://proxy.example.com:5100' + * The hostname and port of the Redis datastore instance. */ - 'proxy' => null, + //'store.redis.host' => 'localhost', + //'store.redis.port' => 6379, /* - * Array of domains that are allowed when generating links or redirections - * to URLs. simpleSAMLphp will use this option to determine whether to - * to consider a given URL valid or not, but you should always validate - * URLs obtained from the input on your own (i.e. ReturnTo or RelayState - * parameters obtained from the $_REQUEST array). - * - * Set to NULL to disable checking of URLs. - * - * simpleSAMLphp will automatically add your own domain (either by checking - * it dinamically, or by using the domain defined in the 'baseurlpath' - * directive, the latter having precedence) to the list of trusted domains, - * in case this option is NOT set to NULL. In that case, you are explicitly - * telling simpleSAMLphp to verify URLs. - * - * Set to an empty array to disallow ALL redirections or links pointing to - * an external URL other than your own domain. - * - * Example: - * 'trusted.url.domains' => array('sp.example.com', 'app.example.com'), + * The prefix we should use on our Redis datastore. */ - 'trusted.url.domains' => null, - + //'store.redis.prefix' => 'SimpleSAMLphp', ]; + +if ($HUB_MODE) { + // prefix the 'member' (urn:oid:2.5.4.31) attribute elements with idp.idp_name. + $config['authproc.idp'][48] = 'sildisco:TagGroup'; + $config['authproc.idp'][49] = 'sildisco:AddIdp2NameId'; +} diff --git a/development/idp2-local/metadata/saml20-idp-hosted.php b/development/idp2-local/metadata/saml20-idp-hosted.php index bb380850..ad7b8705 100644 --- a/development/idp2-local/metadata/saml20-idp-hosted.php +++ b/development/idp2-local/metadata/saml20-idp-hosted.php @@ -21,5 +21,9 @@ * Authentication source to use. Must be one that is configured in * 'config/authsources.php'. */ - 'auth' => 'admin', + 'auth' => 'example-userpass', ]; + +// Copy configuration for port 80 and modify host. +$metadata['http://ssp-idp2.local'] = $metadata['http://ssp-idp2.local:8086']; +$metadata['http://ssp-idp2.local']['host'] = 'ssp-idp2.local'; diff --git a/development/idp3-local/cert/ssp-hub-idp2.crt b/development/idp3-local/cert/ssp-hub-idp2.crt new file mode 100644 index 00000000..bcbf054d --- /dev/null +++ b/development/idp3-local/cert/ssp-hub-idp2.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIJALBaUrvz1X5DMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANT +SUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkB +FhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE4MTQwMDUxWhcNMjYxMDE4 +MTQwMDUxWjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldh +eGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2 +ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx5mZNwjEnakJho+5etuFyx+2g9rs96iL +X/LDC24aBAsdNxTNuIc1jJ7pxBxGrepEND4LkietLNBlOr1q50nq2+ddTrCfmoJB ++9BqBOxcm9qWeqWbp8/arUjaxPzK3DfZrxJxIVFjzqFF7gI91y9yvEW/fqLRMhvn +H1ns+N1ne59zr1y6h9mmHfBffGr1YXAfyEAuV1ich4AfTfjqhdwFwxhFLLCVnxA0 +bDbNw/0eGCSiA13N7a013xTurLeJu0AQaZYssMqvc/17UphH4gWDMEZAwy0EfRSB +OsDOYCxeNxVajnWX1834VDpBDfpnZj996Gh8tzRQxQgT9/plHKhGiwIDAQABo1Aw +TjAdBgNVHQ4EFgQUApxlUQg26GrG3eH8lEG3SkqbH/swHwYDVR0jBBgwFoAUApxl +UQg26GrG3eH8lEG3SkqbH/swDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AQEANhbm8WgIqBDlF7DIRVUbq04TEA9nOJG8wdjJYdoKrPX9f/E9slkFuD2StcK9 +9RTcowa8Z2OmW7tksa+onyH611Lq21QXh4aHzQUAm2HbsmPQRZnkByeYoCJ/1tuE +ho+x+VGanaUICSBVWYiebAQVKHR6miFypRElibNBizm2nqp6Q9B87V8COzyDVngR +1DlWDduxYaNOBgvht3Rk9Y2pVHqym42dIfN+pprcsB1PGBkY/BngIuS/aqTENbmo +C737vcb06e8uzBsbCpHtqUBjPpL2psQZVJ2Y84JmHafC3B7nFQrjdZBbc9eMHfPo +240Rh+pDLwxdxPqRAZdeLaUkCQ== +-----END CERTIFICATE----- diff --git a/development/idp3-local/cert/ssp-hub-idp2.pem b/development/idp3-local/cert/ssp-hub-idp2.pem new file mode 100644 index 00000000..7674ef99 --- /dev/null +++ b/development/idp3-local/cert/ssp-hub-idp2.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHmZk3CMSdqQmG +j7l624XLH7aD2uz3qItf8sMLbhoECx03FM24hzWMnunEHEat6kQ0PguSJ60s0GU6 +vWrnSerb511OsJ+agkH70GoE7Fyb2pZ6pZunz9qtSNrE/MrcN9mvEnEhUWPOoUXu +Aj3XL3K8Rb9+otEyG+cfWez43Wd7n3OvXLqH2aYd8F98avVhcB/IQC5XWJyHgB9N ++OqF3AXDGEUssJWfEDRsNs3D/R4YJKIDXc3trTXfFO6st4m7QBBpliywyq9z/XtS +mEfiBYMwRkDDLQR9FIE6wM5gLF43FVqOdZfXzfhUOkEN+mdmP33oaHy3NFDFCBP3 ++mUcqEaLAgMBAAECggEACinHBGdc44483u4oipns6RfXSkV2dXHOjvckeUuE5ZnP +RgO4KeIwltVsn8C01JwuFt7l5e5BQhvmW6RTci1wWPwh4yTZK5vgUjsdetyyJnlt +2hbeox/RSauBADDC/42Udvagbgrf4yCRF/pjPba7x9xhUMhnkH6dORpyF4XmhAPW +TVCA7VVRL5aoEfemiZYOpjPkY135QqI6/PaLbRDUkqUtKdAB2+/XRTF2K8gbb44x +f/wZeqpOG1y82P3aYVM1f3RLQUAS0rKyQJBRB8fHy5LY2z9LAlC8KSp1BAIKtqMT +lUr6MIs2oImrLL0JyvEbcmtZI4MdGgnmkxrjc/8ZYQKBgQD8t18HVfmTu+5HZCuv +NItpLOu/uxm6UwwAwbljtM2K2562wCsu9/tt72V0Ismysz19VUva/FtSqksuOWcA +HC+APHtWMMtsBcQMZGrFHUlJCKv963gu7CoeJvY3mSWm4t8xuZBSz5pAeoeENioH +NrL4+K2+cmVGRNjKIDipN5Ng8QKBgQDKMYrqNmH8/IaIDTi27d1A+1YTEZe/toaP +YbTyyQ731mLwnukAx1MhFgoXe294nXiD3tC0g6ISpFgyUTL5OplIs/yiXks0y5/G +mKxGsVc5qtBQB8utA3i8EzT6x2fIYmuJY2Pj3r6jFFzqOjlILN8ct1v05qjKH+gM +n5C/IC/fOwKBgQCEcubPRXQkxZ5AtHNgxD08xlpYhosZaGUmEGJFq4D+gdRRG66G +U1nnaEzX7VOg4OgdRBMZlqGWVcJJW7RsDlmm8AwERFaZKvxxMj/zR0IdkPnzfvHi +RcxdOTZaNV3SdZ1cxlCp1jyWBqH33Rtx5G0wp8UHx5Tkmziz1udbaNFJQQKBgQDA +EvpE7i59tqJSQkUbObFSVrB44uCGJW2EbawIa0lF1KoerMbpj3B/4MDrd734FZdz +pkobAWUIUojaG9rReYI914Vp9St6VulMLqKRcUxMIuFK9WzdyYt7Fr/gb2c+q4g+ +dmVhBauRnfl6JJ9f2giE7gZ0Cl5TzKWSwE4v0fLIGwKBgGuiI+2j8YOsV4LYyin+ +9p5qmk4gVUe5ohPUKCdPeaZiiQbAJ3l5B3LR2sgV1mOm996Nm9Y0HEback4ISAjz +Nd3TkcwDVaa7GV9pMknM2rK0U6gupbtPAaTMCanXu2VZbKGfQDlkpE3iYvMsuGIW +1ppvkZ+ZtqGlvPGk+CWjr6vu +-----END PRIVATE KEY----- diff --git a/development/ssp/config/authsources.php b/development/idp3-local/config/authsources.php similarity index 82% rename from development/ssp/config/authsources.php rename to development/idp3-local/config/authsources.php index 0f55fe7b..a5d7d017 100644 --- a/development/ssp/config/authsources.php +++ b/development/idp3-local/config/authsources.php @@ -1,13 +1,13 @@ array( + 'admin' => [ // The default is to use core:AdminPassword, but it can be replaced with // any authentication source. 'core:AdminPassword', - ), + ], -); +]; diff --git a/development/idp3-local/config/config.php b/development/idp3-local/config/config.php new file mode 100644 index 00000000..656009fa --- /dev/null +++ b/development/idp3-local/config/config.php @@ -0,0 +1,804 @@ + $BASE_URL_PATH, + 'certdir' => 'cert/', + 'loggingdir' => 'log/', + 'datadir' => 'data/', + + /* + * A directory where simpleSAMLphp can save temporary files. + * + * SimpleSAMLphp will attempt to create this directory if it doesn't exist. + */ + 'tempdir' => '/tmp/simplesaml', + + + /* + * If you enable this option, simpleSAMLphp will log all sent and received messages + * to the log file. + * + * This option also enables logging of the messages that are encrypted and decrypted. + * + * Note: The messages are logged with the DEBUG log level, so you also need to set + * the 'logging.level' option to LOG_DEBUG. + */ + 'debug' => false, + + /* + * When showerrors is enabled, all error messages and stack traces will be output + * to the browser. + * + * When errorreporting is enabled, a form will be presented for the user to report + * the error to technicalcontact_email. + */ + 'showerrors' => $SHOW_SAML_ERRORS, + 'errorreporting' => false, + + /* + * Custom error show function called from \SimpleSAML\Error\Error::show. + * See docs/simplesamlphp-errorhandling.txt for function code example. + * + * Example: + * 'errors.show_function' => array('\SimpleSAML\Module\example\Error\Show', 'show'), + */ + + /* + * This option allows you to enable validation of XML data against its + * schemas. A warning will be written to the log if validation fails. + */ + 'debug.validatexml' => false, + + /* + * This password must be kept secret, and modified from the default value 123. + * This password will give access to the installation page of simpleSAMLphp with + * metadata listing and diagnostics pages. + * You can also put a hash here; run "bin/pwgen.php" to generate one. + */ + 'auth.adminpassword' => $ADMIN_PASS, + 'admin.protectindexpage' => $ADMIN_PROTECT_INDEX_PAGE, + 'admin.protectmetadata' => true, + + /* + * This is a secret salt used by simpleSAMLphp when it needs to generate a secure hash + * of a value. It must be changed from its default value to a secret value. The value of + * 'secretsalt' can be any valid string of any length. + * + * A possible way to generate a random salt is by running the following command from a unix shell: + * tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' /dev/null;echo + */ + 'secretsalt' => $SECRET_SALT, + + /* + * Some information about the technical persons running this installation. + * The email address will be used as the recipient address for error reports, and + * also as the technical contact in generated metadata. + */ + 'technicalcontact_name' => $ADMIN_NAME, + 'technicalcontact_email' => $ADMIN_EMAIL, + + /* + * The timezone of the server. This option should be set to the timezone you want + * simpleSAMLphp to report the time in. The default is to guess the timezone based + * on your system timezone. + * + * See this page for a list of valid timezones: http://php.net/manual/en/timezones.php + */ + 'timezone' => $TIMEZONE, + + /* + * Logging. + * + * define the minimum log level to log + * \SimpleSAML\Logger::ERR No statistics, only errors + * \SimpleSAML\Logger::WARNING No statistics, only warnings/errors + * \SimpleSAML\Logger::NOTICE Statistics and errors + * \SimpleSAML\Logger::INFO Verbose logs + * \SimpleSAML\Logger::DEBUG Full debug logs - not reccomended for production + * + * Choose logging handler. + * + * Options: [syslog,file,errorlog] + * + */ + 'logging.level' => \SimpleSAML\Logger::NOTICE, + 'logging.handler' => $LOGGING_HANDLER, + + /* + * Specify the format of the logs. Its use varies depending on the log handler used (for instance, you cannot + * control here how dates are displayed when using the syslog or errorlog handlers), but in general the options + * are: + * + * - %date{}: the date and time, with its format specified inside the brackets. See the PHP documentation + * of the strftime() function for more information on the format. If the brackets are omitted, the standard + * format is applied. This can be useful if you just want to control the placement of the date, but don't care + * about the format. + * + * - %process: the name of the SimpleSAMLphp process. Remember you can configure this in the 'logging.processname' + * option below. + * + * - %level: the log level (name or number depending on the handler used). + * + * - %stat: if the log entry is intended for statistical purposes, it will print the string 'STAT ' (bear in mind + * the trailing space). + * + * - %trackid: the track ID, an identifier that allows you to track a single session. + * + * - %srcip: the IP address of the client. If you are behind a proxy, make sure to modify the + * $_SERVER['REMOTE_ADDR'] variable on your code accordingly to the X-Forwarded-For header. + * + * - %msg: the message to be logged. + * + */ + //'logging.format' => '%date{%b %d %H:%M:%S} %process %level %stat[%trackid] %msg', + + /* + * Choose which facility should be used when logging with syslog. + * + * These can be used for filtering the syslog output from simpleSAMLphp into its + * own file by configuring the syslog daemon. + * + * See the documentation for openlog (http://php.net/manual/en/function.openlog.php) for available + * facilities. Note that only LOG_USER is valid on windows. + * + * The default is to use LOG_LOCAL5 if available, and fall back to LOG_USER if not. + */ + 'logging.facility' => defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER, + + /* + * The process name that should be used when logging to syslog. + * The value is also written out by the other logging handlers. + */ + 'logging.processname' => 'simplesamlphp', + + /* Logging: file - Logfilename in the loggingdir from above. + */ + 'logging.logfile' => 'simplesamlphp.log', + + /* (New) statistics output configuration. + * + * This is an array of outputs. Each output has at least a 'class' option, which + * selects the output. + */ + 'statistics.out' => [// Log statistics to the normal log. + /* + [ + 'class' => 'core:Log', + 'level' => 'notice', + ], + */ + // Log statistics to files in a directory. One file per day. + /* + [ + 'class' => 'core:File', + 'directory' => '/var/log/stats', + ], + */ + ], + + + /* + * Enable + * + * Which functionality in simpleSAMLphp do you want to enable. Normally you would enable only + * one of the functionalities below, but in some cases you could run multiple functionalities. + * In example when you are setting up a federation bridge. + */ + 'enable.saml20-idp' => $SAML20_IDP_ENABLE, + 'enable.shib13-idp' => false, + 'enable.adfs-idp' => false, + 'enable.wsfed-sp' => false, + 'enable.authmemcookie' => false, + + + /* + * Module enable configuration + * + * Configuration to override module enabling/disabling. + * + * Example: + * + * 'module.enable' => array( + * // Setting to TRUE enables. + * 'exampleauth' => true, + * // Setting to FALSE disables. + * 'saml' => false, + * // Unset or NULL uses default. + * 'core' => NULL, + * ), + * + */ + + 'module.enable' => [ + // Setting to TRUE enables. + 'authgoogle' => $GOOGLE_ENABLE, + ], + + /* + * This value is the duration of the session in seconds. Make sure that the time duration of + * cookies both at the SP and the IdP exceeds this duration. + */ + 'session.duration' => $SESSION_DURATION, + + /* + * Sets the duration, in seconds, data should be stored in the datastore. As the datastore is used for + * login and logout requests, thid option will control the maximum time these operations can take. + * The default is 4 hours (4*60*60) seconds, which should be more than enough for these operations. + */ + 'session.datastore.timeout' => $SESSION_DATASTORE_TIMEOUT, + + /* + * Sets the duration, in seconds, auth state should be stored. + */ + 'session.state.timeout' => $SESSION_STATE_TIMEOUT, + + /* + * Option to override the default settings for the session cookie name + */ + 'session.cookie.name' => 'SSPSESSID', + + /* + * Expiration time for the session cookie, in seconds. + * + * Defaults to 0, which means that the cookie expires when the browser is closed. + * + * Example: + * 'session.cookie.lifetime' => 30*60, + */ + 'session.cookie.lifetime' => $SESSION_COOKIE_LIFETIME, + + /* + * Limit the path of the cookies. + * + * Can be used to limit the path of the cookies to a specific subdirectory. + * + * Example: + * 'session.cookie.path' => '/simplesaml/', + */ + 'session.cookie.path' => '/', + + /* + * Cookie domain. + * + * Can be used to make the session cookie available to several domains. + * + * Example: + * 'session.cookie.domain' => '.example.org', + */ + 'session.cookie.domain' => null, + + /* + * Set the secure flag in the cookie. + * + * Set this to TRUE if the user only accesses your service + * through https. If the user can access the service through + * both http and https, this must be set to FALSE. + */ + 'session.cookie.secure' => $SECURE_COOKIE, + + /* + * When set to FALSE fallback to transient session on session initialization + * failure, throw exception otherwise. + */ + 'session.disable_fallback' => false, + + /* + * Enable secure POST from HTTPS to HTTP. + * + * If you have some SP's on HTTP and IdP is normally on HTTPS, this option + * enables secure POSTing to HTTP endpoint without warning from browser. + * + * For this to work, module.php/core/postredirect.php must be accessible + * also via HTTP on IdP, e.g. if your IdP is on + * https://idp.example.org/ssp/, then + * http://idp.example.org/ssp/module.php/core/postredirect.php must be accessible. + */ + 'enable.http_post' => false, + + /* + * Options to override the default settings for php sessions. + */ + 'session.phpsession.cookiename' => null, + 'session.phpsession.savepath' => null, + 'session.phpsession.httponly' => true, + + /* + * Option to override the default settings for the auth token cookie + */ + 'session.authtoken.cookiename' => 'SSPAUTHTOKEN', + + /* + * Options for remember me feature for IdP sessions. Remember me feature + * has to be also implemented in authentication source used. + * + * Option 'session.cookie.lifetime' should be set to zero (0), i.e. cookie + * expires on browser session if remember me is not checked. + * + * Session duration ('session.duration' option) should be set according to + * 'session.rememberme.lifetime' option. + * + * It's advised to use remember me feature with session checking function + * defined with 'session.check_function' option. + */ + 'session.rememberme.enable' => false, + 'session.rememberme.checked' => false, + 'session.rememberme.lifetime' => $SESSION_REMEMBERME_LIFETIME, + + /** + * Custom function for session checking called on session init and loading. + * See docs/simplesamlphp-advancedfeatures.txt for function code example. + * + * Example: + * 'session.check_function' => array('\SimpleSAML\Module\example\Util', 'checkSession'), + */ + + /* + * Languages available, RTL languages, and what language is default + */ + 'language.available' => array( + 'en', 'es', 'fr', 'pt', + ), + 'language.rtl' => array('ar', 'dv', 'fa', 'ur', 'he'), + 'language.default' => 'en', + + /* + * Options to override the default settings for the language parameter + */ + 'language.parameter.name' => 'language', + 'language.parameter.setcookie' => true, + + /* + * Options to override the default settings for the language cookie + */ + 'language.cookie.name' => 'language', + 'language.cookie.domain' => null, + 'language.cookie.path' => '/', + 'language.cookie.lifetime' => (60 * 60 * 24 * 900), + + /** + * Custom getLanguage function called from \SimpleSAML\XHTML\Template::getLanguage(). + * Function should return language code of one of the available languages or NULL. + * See \SimpleSAML\XHTML\Template::getLanguage() source code for more info. + * + * This option can be used to implement a custom function for determining + * the default language for the user. + * + * Example: + * 'language.get_language_function' => array('\SimpleSAML\Module\example\Template', 'getLanguage'), + */ + + /* + * Extra dictionary for attribute names. + * This can be used to define local attributes. + * + * The format of the parameter is a string with :. + * + * Specifying this option will cause us to look for modules//dictionaries/.definition.json + * The dictionary should look something like: + * + * { + * "firstattribute": { + * "en": "English name", + * "no": "Norwegian name" + * }, + * "secondattribute": { + * "en": "English name", + * "no": "Norwegian name" + * } + * } + * + * Note that all attribute names in the dictionary must in lowercase. + * + * Example: 'attributes.extradictionary' => 'ourmodule:ourattributes', + */ + 'attributes.extradictionary' => null, + + /* + * Which theme directory should be used? + */ + 'theme.use' => $THEME_USE, + + + /* + * Default IdP for WS-Fed. + */ + // 'default-wsfed-idp' => 'urn:federation:pingfederate:localhost', + + /* + * Whether the discovery service should allow the user to save his choice of IdP. + */ + 'idpdisco.enableremember' => true, + 'idpdisco.rememberchecked' => true, + + // Disco service only accepts entities it knows. + 'idpdisco.validate' => true, + + 'idpdisco.extDiscoveryStorage' => null, + + /* + * IdP Discovery service look configuration. + * Wether to display a list of idp or to display a dropdown box. For many IdP' a dropdown box + * gives the best use experience. + * + * When using dropdown box a cookie is used to highlight the previously chosen IdP in the dropdown. + * This makes it easier for the user to choose the IdP + * + * Options: [links,dropdown] + * + */ + 'idpdisco.layout' => 'links', + + /* + * Whether simpleSAMLphp should sign the response or the assertion in SAML 1.1 authentication + * responses. + * + * The default is to sign the assertion element, but that can be overridden by setting this + * option to TRUE. It can also be overridden on a pr. SP basis by adding an option with the + * same name to the metadata of the SP. + */ + 'shib13.signresponse' => true, + + + /* + * Authentication processing filters that will be executed for all IdPs + * Both Shibboleth and SAML 2.0 + */ + 'authproc.idp' => [ + /* Enable the authproc filter below to add URN Prefixces to all attributes + 10 => array( + 'class' => 'core:AttributeMap', 'addurnprefix' + ), */ + /* Enable the authproc filter below to automatically generated eduPersonTargetedID. + 20 => 'core:TargetedID', + */ + + // Adopts language from attribute to use in UI + 30 => 'core:LanguageAdaptor', + + /* Add a realm attribute from edupersonprincipalname + 40 => 'core:AttributeRealm', + */ + 45 => [ + 'class' => 'core:StatisticsWithAttribute', + 'attributename' => 'realm', + 'type' => 'saml20-idp-SSO', + ], + + // Add one to help with testing + 50 => [ + 'class' => 'core:AttributeAdd', + 'eduPersonPrincipalName' => 'TEST_ADMIN', + 'urn:oid:0.9.2342.19200300.100.1.3' => 'test_admin@idp3.org', + 'uid' => '333366', + ], + + // Use the uid value to populate the nameid entry + 60 => [ + 'class' => 'saml:AttributeNameID', + 'attribute' => 'uid', + 'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', + ], + + /* + * Search attribute "distinguishedName" for pattern and replaces if found + + 70 => array( + 'class' => 'core:AttributeAlter', + 'pattern' => '/OU=studerende/', + 'replacement' => 'Student', + 'subject' => 'distinguishedName', + '%replace', + ), + */ + + /* + * Consent module is enabled (with no permanent storage, using cookies). + + 90 => array( + 'class' => 'consent:Consent', + 'store' => 'consent:Cookie', + 'focus' => 'yes', + 'checked' => true + ), + */ + + + // If language is set in Consent module it will be added as an attribute. + 99 => 'core:LanguageAdaptor', + ], + /* + * Authentication processing filters that will be executed for all SPs + * Both Shibboleth and SAML 2.0 + */ + 'authproc.sp' => [ + /* + 10 => array( + 'class' => 'core:AttributeMap', 'removeurnprefix' + ), + */ + + /* + * Generate the 'group' attribute populated from other variables, including eduPersonAffiliation. + 60 => array( + 'class' => 'core:GenerateGroups', 'eduPersonAffiliation' + ), + */ + /* + * All users will be members of 'users' and 'members' + 61 => array( + 'class' => 'core:AttributeAdd', 'groups' => array('users', 'members') + ), + */ + + // Adopts language from attribute to use in UI + 90 => 'core:LanguageAdaptor', + + ], + + + /* + * This option configures the metadata sources. The metadata sources is given as an array with + * different metadata sources. When searching for metadata, simpleSAMPphp will search through + * the array from start to end. + * + * Each element in the array is an associative array which configures the metadata source. + * The type of the metadata source is given by the 'type' element. For each type we have + * different configuration options. + * + * Flat file metadata handler: + * - 'type': This is always 'flatfile'. + * - 'directory': The directory we will load the metadata files from. The default value for + * this option is the value of the 'metadatadir' configuration option, or + * 'metadata/' if that option is unset. + * + * XML metadata handler: + * This metadata handler parses an XML file with either an EntityDescriptor element or an + * EntitiesDescriptor element. The XML file may be stored locally, or (for debugging) on a remote + * web server. + * The XML hetadata handler defines the following options: + * - 'type': This is always 'xml'. + * - 'file': Path to the XML file with the metadata. + * - 'url': The URL to fetch metadata from. THIS IS ONLY FOR DEBUGGING - THERE IS NO CACHING OF THE RESPONSE. + * + * + * Examples: + * + * This example defines two flatfile sources. One is the default metadata directory, the other + * is a metadata directory with autogenerated metadata files. + * + * 'metadata.sources' => array( + * array('type' => 'flatfile'), + * array('type' => 'flatfile', 'directory' => 'metadata-generated'), + * ), + * + * This example defines a flatfile source and an XML source. + * 'metadata.sources' => array( + * array('type' => 'flatfile'), + * array('type' => 'xml', 'file' => 'idp.example.org-idpMeta.xml'), + * ), + * + * + * Default: + * 'metadata.sources' => array( + * array('type' => 'flatfile') + * ), + */ + 'metadata.sources' => [ + ['type' => 'flatfile'], + ], + + + /* + * Configure the datastore for simpleSAMLphp. + * + * - 'phpsession': Limited datastore, which uses the PHP session. + * - 'memcache': Key-value datastore, based on memcache. + * - 'sql': SQL datastore, using PDO. + * + * The default datastore is 'phpsession'. + * + * (This option replaces the old 'session.handler'-option.) + */ + 'store.type' => 'phpsession', + + + /* + * The DSN the sql datastore should connect to. + * + * See http://www.php.net/manual/en/pdo.drivers.php for the various + * syntaxes. + */ + 'store.sql.dsn' => 'sqlite:/path/to/sqlitedatabase.sq3', + + /* + * The username and password to use when connecting to the database. + */ + 'store.sql.username' => null, + 'store.sql.password' => null, + + /* + * The prefix we should use on our tables. + */ + 'store.sql.prefix' => 'simpleSAMLphp', + + + /* + * Configuration for the MemcacheStore class. This allows you to store + * multiple redudant copies of sessions on different memcache servers. + * + * 'memcache_store.servers' is an array of server groups. Every data + * item will be mirrored in every server group. + * + * Each server group is an array of servers. The data items will be + * load-balanced between all servers in each server group. + * + * Each server is an array of parameters for the server. The following + * options are available: + * - 'hostname': This is the hostname or ip address where the + * memcache server runs. This is the only required option. + * - 'port': This is the port number of the memcache server. If this + * option isn't set, then we will use the 'memcache.default_port' + * ini setting. This is 11211 by default. + * - 'weight': This sets the weight of this server in this server + * group. http://php.net/manual/en/function.Memcache-addServer.php + * contains more information about the weight option. + * - 'timeout': The timeout for this server. By default, the timeout + * is 3 seconds. + * + * Example of redudant configuration with load balancing: + * This configuration makes it possible to lose both servers in the + * a-group or both servers in the b-group without losing any sessions. + * Note that sessions will be lost if one server is lost from both the + * a-group and the b-group. + * + * 'memcache_store.servers' => array( + * array( + * array('hostname' => 'mc_a1'), + * array('hostname' => 'mc_a2'), + * ), + * array( + * array('hostname' => 'mc_b1'), + * array('hostname' => 'mc_b2'), + * ), + * ), + * + * Example of simple configuration with only one memcache server, + * running on the same computer as the web server: + * Note that all sessions will be lost if the memcache server crashes. + * + * 'memcache_store.servers' => array( + * array( + * array('hostname' => 'localhost'), + * ), + * ), + * + */ + 'memcache_store.servers' => [ + [ + ['hostname' => 'localhost'], + ], + ], + + + /* + * This value is the duration data should be stored in memcache. Data + * will be dropped from the memcache servers when this time expires. + * The time will be reset every time the data is written to the + * memcache servers. + * + * This value should always be larger than the 'session.duration' + * option. Not doing this may result in the session being deleted from + * the memcache servers while it is still in use. + * + * Set this value to 0 if you don't want data to expire. + * + * Note: The oldest data will always be deleted if the memcache server + * runs out of storage space. + */ + 'memcache_store.expires' => 36 * (60 * 60), // 36 hours. + + + /* + * Should signing of generated metadata be enabled by default. + * + * Metadata signing can also be enabled for a individual SP or IdP by setting the + * same option in the metadata for the SP or IdP. + */ + 'metadata.sign.enable' => true, + + /* + * The default key & certificate which should be used to sign generated metadata. These + * are files stored in the cert dir. + * These values can be overridden by the options with the same names in the SP or + * IdP metadata. + * + * If these aren't specified here or in the metadata for the SP or IdP, then + * the 'certificate' and 'privatekey' option in the metadata will be used. + * if those aren't set, signing of metadata will fail. + */ + 'metadata.sign.privatekey' => 'ssp-hub.pem', + 'metadata.sign.privatekey_pass' => null, + 'metadata.sign.certificate' => 'ssp-hub.crt', + + + /* + * Proxy to use for retrieving URLs. + * + * Example: + * 'proxy' => 'tcp://proxy.example.com:5100' + */ + 'proxy' => null, + + /* + * Array of domains that are allowed when generating links or redirections + * to URLs. simpleSAMLphp will use this option to determine whether to + * to consider a given URL valid or not, but you should always validate + * URLs obtained from the input on your own (i.e. ReturnTo or RelayState + * parameters obtained from the $_REQUEST array). + * + * Set to NULL to disable checking of URLs. + * + * simpleSAMLphp will automatically add your own domain (either by checking + * it dinamically, or by using the domain defined in the 'baseurlpath' + * directive, the latter having precedence) to the list of trusted domains, + * in case this option is NOT set to NULL. In that case, you are explicitly + * telling simpleSAMLphp to verify URLs. + * + * Set to an empty array to disallow ALL redirections or links pointing to + * an external URL other than your own domain. + * + * Example: + * 'trusted.url.domains' => array('sp.example.com', 'app.example.com'), + */ + 'trusted.url.domains' => null, + +]; diff --git a/development/idp3-local/metadata/saml20-idp-hosted.php b/development/idp3-local/metadata/saml20-idp-hosted.php new file mode 100644 index 00000000..37d487af --- /dev/null +++ b/development/idp3-local/metadata/saml20-idp-hosted.php @@ -0,0 +1,25 @@ + '__DEFAULT__', + + // X.509 key and certificate. Relative to the cert directory. + 'privatekey' => 'ssp-hub-idp2.pem', + 'certificate' => 'ssp-hub-idp2.crt', + + /* + * Authentication source to use. Must be one that is configured in + * 'config/authsources.php'. + */ + 'auth' => 'admin', +]; diff --git a/development/idp3-local/metadata/saml20-sp-remote.php b/development/idp3-local/metadata/saml20-sp-remote.php new file mode 100644 index 00000000..ad92fd8b --- /dev/null +++ b/development/idp3-local/metadata/saml20-sp-remote.php @@ -0,0 +1,16 @@ + 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', + 'AssertionConsumerService' => 'http://ssp-hub.local/module.php/sildisco/sp/saml2-acs.php/hub-discovery', + 'SingleLogoutService' => 'http://ssp-hub.local/module.php/sildisco/sp/saml2-logout.php/hub-discovery', + 'certData' => 'MIIDzzCCAregAwIBAgIJANuvVcQPANecMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIzMTEyWhcNMjYxMDE3MTIzMTEyWjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxAimEkw4Teyf/gZelL7OuQYg/JbDIKHPXJhLPBm/HK6pM5ZZKydVXTdMgMqkl4xK+xZ2CnkozsUiMLhAuWBsX9Dcz1M4SkPRwk4puFhXzsp7fKIVP43zUhF7p2TmbernrrIQHjg6PuegKmCGyiKUpukcYvf2RXNwHwJx+Uq0zLP4PgBSrQ2t1eKZ1jQ+noBb1NqOuy969WRYmN4EmjXDuJB9d+b3GwtbZToWgiFxFjd/NN9BFJXZEaLzRj5LAq5bu2vPPDZDarHFMRUzVJ91eafoaz6zpR1iUGj9zR+y2sUPxD/fJMZ+4AHWA2LOrTBBIuuWbp96yvcJ4WjmlfhcFQIDAQABo1AwTjAdBgNVHQ4EFgQUkJFAMJdr2lXsuezS6pDXHnmJspMwHwYDVR0jBBgwFoAUkJFAMJdr2lXsuezS6pDXHnmJspMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAOEPbchaUr45L5i+ueookevsABYnltwJZ4rYJbF9VURPcEhB6JxTMZqb4s113ftHvVYfoAfLYZ9swETaHL+esx41yAebf0kWpQ3f63S5F2FcrTj+HP0XsvW/EDrvaTKM9jnKPNmbXrpq06eaUZfkVL0TAUsxYTKkttTSTiESEzp5wzYyhp7l3kpHhEvGOlh5suYjnZ2HN0uxscCR6PS47H6TMMEZuG032DWDC016/JniWvERtpf4Yw26V+I9xevp2E2MPcZne31Pe3sCh4Wpe4cV/SCFqZHlpnH96ncz4F+KvmmhbEx5VPhQSJNFIWEvI86k+lTNQOqj6YVvGvq95LQ==', +]; diff --git a/development/init-dynamodb.sh b/development/init-dynamodb.sh new file mode 100755 index 00000000..f944c583 --- /dev/null +++ b/development/init-dynamodb.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env ash + +# Create data table +aws dynamodb create-table --table-name sildisco_local_user-log \ + --attribute-definitions AttributeName=ID,AttributeType=S \ + --key-schema AttributeName=ID,KeyType=HASH \ + --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=10 \ + --endpoint-url http://dynamo:8000 + + +# Enable Time to Live +aws dynamodb update-time-to-live --table-name sildisco_local_user-log \ + --time-to-live-specification "Enabled=true,AttributeName=ExpiresAt" \ + --endpoint-url http://dynamo:8000 \ No newline at end of file diff --git a/development/sp-local/config/authsources-pwmanager.php b/development/sp-local/config/authsources-pwmanager.php new file mode 100644 index 00000000..80aee9c9 --- /dev/null +++ b/development/sp-local/config/authsources-pwmanager.php @@ -0,0 +1,28 @@ + [ + // The default is to use core:AdminPassword, but it can be replaced with + // any authentication source. + + 'core:AdminPassword', + ], + + 'mfa-idp' => [ + 'saml:SP', + 'entityID' => 'http://pwmanager.local:8084', + 'idp' => 'http://ssp-idp1.local:8085', + 'discoURL' => null, + 'NameIDPolicy' => "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + ], + + 'mfa-idp-no-port' => [ + 'saml:SP', + 'entityID' => 'http://pwmanager.local', + 'idp' => 'http://ssp-idp1.local', + 'discoURL' => null, + 'NameIDPolicy' => "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + ], +]; diff --git a/development/sp2-local/config/authsources.php b/development/sp2-local/config/authsources.php index b48c271c..60f76215 100644 --- a/development/sp2-local/config/authsources.php +++ b/development/sp2-local/config/authsources.php @@ -27,6 +27,9 @@ // The URL to the discovery service. // Can be NULL/unset, in which case a builtin discovery service will be used. 'discoURL' => null, + + // Specify what private key to use (such as for decrypting assertions). + 'privatekey' => 'ssp-hub-sp2.pem', ], 'ssp-hub-custom-port' => [ diff --git a/development/sp3-local/cert/ssp-hub-sp3.crt b/development/sp3-local/cert/ssp-hub-sp3.crt new file mode 100644 index 00000000..21ed1c2b --- /dev/null +++ b/development/sp3-local/cert/ssp-hub-sp3.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIJAPnOHgSgAeNrMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANT +SUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkB +FhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIyNzU2WhcNMjYxMDE3 +MTIyNzU2WjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldh +eGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2 +ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0u+mXWS8vUkKjtJcK1hd0iGW2vbTvYos +gyDdqClcSzwpbWJg1A1ChuiQIf7S+5bWL2AN4zMoem/JTn7cE9octqU34ZJAyP/c +esppA9G53F9gH4XdoPgnWsb8vdWooDDUk+asc7ah/XwKixQNcELPDZkOba5+pqoK +GjMxfL7JQ6+P6LB+xItzvLBXU4+onbGPIF6pmZ8S74mt0J62Y6ne40BHx8FdrtBg +dk5TFcDedW09rRJrTFpi3hGSUkcjqj84B+oLAb08Z0SHoELMp5Yh7Tg5QZ2c+S8I +47tQjV72rNhUYhIyFuImzSg27R7aRJ6Jj6sK4zEg0Ai4VhO4RmgyzwIDAQABo1Aw +TjAdBgNVHQ4EFgQUgkYcMbT0o8kmxAz2O3+p1lDVj1MwHwYDVR0jBBgwFoAUgkYc +MbT0o8kmxAz2O3+p1lDVj1MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AQEANgyTgMVRghgL8klqvZvQpfh80XDPTZotJCc8mZJZ98YkNC8jnR2RIUJpah+X +rgotlKNDOK3HMNuyKGgYcqcno4PdDXKbqp4yXmywdNbbEHwPWDGqZXULw2az+UVw +PUZJcJyJuwJjy3diCJT53N9G0LqXfeEsV0OPQPaB2PWgYNraBd59fckmBTc298Hu +vsHtxUcoXM53ms2Ck6GygGwH1vCg7qyIRRQFL4DiSlnoS8jxt3IIpZZs9FAl1ejt +FBepSne9kEo7lLhAWY1TQqRrRXNHngG/L70ZkZonE9TNK/9xIHuaawqWkV6WLnkh +T0DHCOw67GP97MWzceyFw+n9Vg== +-----END CERTIFICATE----- diff --git a/development/sp3-local/cert/ssp-hub-sp3.pem b/development/sp3-local/cert/ssp-hub-sp3.pem new file mode 100644 index 00000000..14cce0d7 --- /dev/null +++ b/development/sp3-local/cert/ssp-hub-sp3.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDS76ZdZLy9SQqO +0lwrWF3SIZba9tO9iiyDIN2oKVxLPCltYmDUDUKG6JAh/tL7ltYvYA3jMyh6b8lO +ftwT2hy2pTfhkkDI/9x6ymkD0bncX2Afhd2g+Cdaxvy91aigMNST5qxztqH9fAqL +FA1wQs8NmQ5trn6mqgoaMzF8vslDr4/osH7Ei3O8sFdTj6idsY8gXqmZnxLvia3Q +nrZjqd7jQEfHwV2u0GB2TlMVwN51bT2tEmtMWmLeEZJSRyOqPzgH6gsBvTxnRIeg +QsynliHtODlBnZz5Lwjju1CNXvas2FRiEjIW4ibNKDbtHtpEnomPqwrjMSDQCLhW +E7hGaDLPAgMBAAECggEBALkdC5kmkORkt1lDjxOTBzMjuyoKNyQ9oHarXxr2wUJd +V9Xg4iz2Pg37BpJu+WVFqE4HM+jRupJIjBfRCP57CXvYXsQc/7HlqO4xuBtb8IpP +QSIo7qkXXiIyQxet68A5WjU52NnryxmTxATt4iVE3ESIr7rdydQloZwAlUtue15j +mWOyznPK+l4lOeRyibYtFGoHzp4cdhUU2oWxFOEht7F4aGz2SMVGkFfrnRAShmyO +DYliGhEAVFWfnQwXsggXp7c0uBuvrb/kYZc1z6THQ4COBbzbp6NPOJu4yRez+IIs +lNbSYaV9N5EduNt5yayOI1s1Fi8wRm30f+Mgcb/zT2ECgYEA9/P0kl/Tho6ixTnx +furvdvzEW218UderluBooAzEmPKyy1rQQ/rPOcnGYg6SccYSFnJpp5LC128BpQWe +3v4j5c2XsK99PtP5aem5s2NsiZH/ehuTAJnXz1korG5hVGv+bpqK5KhaB7/a3Tm0 +JxVo/fx6iHsHXDGcxAiCzgy36+kCgYEA2cgmrXzqyK9SyPTuFgDjm750h9B7poyJ +DICmoo6FzXPHfjxJyP0xOXtq+nT0ujEDoq/kxs8iLCCW+FSfFuzITylg//0zESvE +9mcfMc6UFO8rS088CtyrsqtIOWG5xYOjrcUA92tiqkWMnklssFxjeNIrojsf/sIJ +AdzYXmx6zfcCgYEA6YP2jLf0xV+lyesFFgt6VOw+nQBiuc1My34y6rC7onPHkR7I +z4zxBrKRxB2HK+FnfX5pJKliGHRx7xF5Cvf7pNxYBM1xPe9ykJ3PBzQWrwUxvrUj +X8iDZ8LHPIWD4ncGmvGu5yPqDixQmlJS6RAP3kuettRvHROYWULOtfFicakCgYAJ +hhk66QWTdSdXpm5rA+rwOqn57oIZzHeJ1m5zGWx8iZ2lxZkscvYeH2mUPl0db1tL +WAnXL+O8rkgr3/d9FynDXHnjd/0tuQ5KAER69x++sp7gEjz79J6Fl7v21nE7VABq +bv0V1Nphu9zkZy2boM6wz/Acjh1eFLo0HKZRqsjMDQKBgQDDiB5QL94aVx/NTJ+y +FWTUmzwJ4HvnOxh2bmFomtU76FhevbK/R6aq85gHPQuD541KpIobTGBJlk9BPelX +V6BicmA/DGxd8aCg99Bn8haTUBTUNAWQJ5FdFmICHY5xul3oHHAs33FCNnx1ETOY +uA/4kr7SqT4LqoMXbsth3UHQhA== +-----END PRIVATE KEY----- diff --git a/development/sp3-local/config/authsources.php b/development/sp3-local/config/authsources.php new file mode 100644 index 00000000..4559d445 --- /dev/null +++ b/development/sp3-local/config/authsources.php @@ -0,0 +1,53 @@ + [ + // The default is to use core:AdminPassword, but it can be replaced with + // any authentication source. + + 'core:AdminPassword', + ], + + + // An authentication source which can authenticate against both SAML 2.0 + // and Shibboleth 1.3 IdPs. + 'ssp-hub' => [ + 'saml:SP', + + // The entity ID of this SP. + // Can be NULL/unset, in which case an entity ID is generated based on the metadata URL. + 'entityID' => 'http://ssp-sp3.local', + + // The entity ID of the IdP this should SP should contact. + // Can be NULL/unset, in which case the user will be shown a list of available IdPs. + 'idp' => 'ssp-hub.local', + + // The URL to the discovery service. + // Can be NULL/unset, in which case a builtin discovery service will be used. + 'discoURL' => null, + + // Specify what private key to use (such as for decrypting assertions). + 'privatekey' => 'ssp-hub-sp3.pem', + ], + + 'ssp-hub-custom-port' => [ + 'saml:SP', + + // The entity ID of this SP. + // Can be NULL/unset, in which case an entity ID is generated based on the metadata URL. + 'entityID' => 'http://ssp-sp3.local:8083', + + // The entity ID of the IdP this should SP should contact. + // Can be NULL/unset, in which case the user will be shown a list of available IdPs. + 'idp' => 'ssp-hub.local', + + // The URL to the discovery service. + // Can be NULL/unset, in which case a builtin discovery service will be used. + 'discoURL' => null, + + // Specify what private key to use (such as for decrypting assertions). + 'privatekey' => 'ssp-hub-sp3.pem', + ], +]; diff --git a/development/sp3-local/config/config.php b/development/sp3-local/config/config.php new file mode 100644 index 00000000..1686e5a2 --- /dev/null +++ b/development/sp3-local/config/config.php @@ -0,0 +1,844 @@ + SimpleSAML\Logger::ERR, // No statistics, only errors + 'WARNING' => SimpleSAML\Logger::WARNING, // No statistics, only warnings/errors + 'NOTICE' => SimpleSAML\Logger::NOTICE, // Statistics and errors + 'INFO' => SimpleSAML\Logger::INFO, // Verbose logs + 'DEBUG' => SimpleSAML\Logger::DEBUG, // Full debug logs - not recommended for production +]; + +/* + * Get config settings from ENV vars or set defaults + */ + +// Required to be defined in environment +$ADMIN_EMAIL = Env::get('ADMIN_EMAIL'); +$ADMIN_PASS = Env::get('ADMIN_PASS'); +$SECRET_SALT = Env::get('SECRET_SALT'); + +// Defaults provided if not defined in environment +$BASE_URL_PATH = Env::get('BASE_URL_PATH', '/'); +$ADMIN_NAME = Env::get('ADMIN_NAME', 'SAML Admin'); +$ADMIN_PROTECT_INDEX_PAGE = Env::get('ADMIN_PROTECT_INDEX_PAGE', true); +$SHOW_SAML_ERRORS = Env::get('SHOW_SAML_ERRORS', false); +$TIMEZONE = Env::get('TIMEZONE', 'GMT'); +$ENABLE_DEBUG = Env::get('ENABLE_DEBUG', false); +$LOGGING_LEVEL = Env::get('LOGGING_LEVEL', 'NOTICE'); +$LOGGING_HANDLER = Env::get('LOGGING_HANDLER', 'stderr'); +$SESSION_DURATION = (int)(Env::get('SESSION_DURATION', 540)); +$SESSION_DATASTORE_TIMEOUT = (int)(Env::get('SESSION_DATASTORE_TIMEOUT', (4 * 60 * 60))); // 4 hours +$SESSION_STATE_TIMEOUT = (int)(Env::get('SESSION_STATE_TIMEOUT', (60 * 60))); // 1 hour +$SESSION_COOKIE_LIFETIME = (int)(Env::get('SESSION_COOKIE_LIFETIME', 0)); +$SESSION_REMEMBERME_LIFETIME = (int)(Env::get('SESSION_REMEMBERME_LIFETIME', (14 * 86400))); // 14 days +$SECURE_COOKIE = Env::get('SECURE_COOKIE', true); +$THEME_USE = Env::get('THEME_USE', 'default'); +$SAML20_IDP_ENABLE = Env::get('SAML20_IDP_ENABLE', true); +$GOOGLE_ENABLE = Env::get('GOOGLE_ENABLE', false); + +$config = [ + + /* + * Setup the following parameters to match the directory of your installation. + * See the user manual for more details. + * + * Valid format for baseurlpath is: + * [(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/] + * (note that it must end with a '/') + * + * The full url format is useful if your simpleSAMLphp setup is hosted behind + * a reverse proxy. In that case you can specify the external url here. + * + * Please note that simpleSAMLphp will then redirect all queries to the + * external url, no matter where you come from (direct access or via the + * reverse proxy). + */ + 'baseurlpath' => $BASE_URL_PATH, + 'certdir' => 'cert/', + 'loggingdir' => 'log/', + 'datadir' => 'data/', + + /* + * A directory where simpleSAMLphp can save temporary files. + * + * SimpleSAMLphp will attempt to create this directory if it doesn't exist. + */ + 'tempdir' => '/tmp/simplesaml', + + + /* + * The 'debug' option allows you to control how SimpleSAMLphp behaves in certain + * situations where further action may be taken + * + * It can be left unset, in which case, debugging is switched off for all actions. + * If set, it MUST be an array containing the actions that you want to enable, or + * alternatively a hashed array where the keys are the actions and their + * corresponding values are booleans enabling or disabling each particular action. + * + * SimpleSAMLphp provides some pre-defined actions, though modules could add new + * actions here. Refer to the documentation of every module to learn if they + * allow you to set any more debugging actions. + * + * The pre-defined actions are: + * + * - 'saml': this action controls the logging of SAML messages exchanged with other + * entities. When enabled ('saml' is present in this option, or set to true), all + * SAML messages will be logged, including plaintext versions of encrypted + * messages. + * + * - 'backtraces': this action controls the logging of error backtraces. If you + * want to log backtraces so that you can debug any possible errors happening in + * SimpleSAMLphp, enable this action (add it to the array or set it to true). + * + * - 'validatexml': this action allows you to validate SAML documents against all + * the relevant XML schemas. SAML 1.1 messages or SAML metadata parsed with + * the XML to SimpleSAMLphp metadata converter or the metaedit module will + * validate the SAML documents if this option is enabled. + * + * If you want to disable debugging completely, unset this option or set it to an + * empty array. + */ + 'debug' => [ + 'saml' => $ENABLE_DEBUG, + 'backtraces' => true, + 'validatexml' => $ENABLE_DEBUG, + ], + + /* + * When showerrors is enabled, all error messages and stack traces will be output + * to the browser. + * + * When errorreporting is enabled, a form will be presented for the user to report + * the error to technicalcontact_email. + */ + 'showerrors' => $SHOW_SAML_ERRORS, + 'errorreporting' => false, + + /* + * Custom error show function called from SimpleSAML_Error_Error::show. + * See docs/simplesamlphp-errorhandling.txt for function code example. + * + * Example: + * 'errors.show_function' => array('sspmod_example_Error_Show', 'show'), + */ + + /* + * This option allows you to enable validation of XML data against its + * schemas. A warning will be written to the log if validation fails. + */ + 'debug.validatexml' => false, + + /* + * This password must be kept secret, and modified from the default value 123. + * This password will give access to the installation page of simpleSAMLphp with + * metadata listing and diagnostics pages. + * You can also put a hash here; run "bin/pwgen.php" to generate one. + */ + 'auth.adminpassword' => $ADMIN_PASS, + 'admin.protectindexpage' => $ADMIN_PROTECT_INDEX_PAGE, + 'admin.protectmetadata' => true, + + /* + * This is a secret salt used by simpleSAMLphp when it needs to generate a secure hash + * of a value. It must be changed from its default value to a secret value. The value of + * 'secretsalt' can be any valid string of any length. + * + * A possible way to generate a random salt is by running the following command from a unix shell: + * tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' /dev/null;echo + */ + 'secretsalt' => $SECRET_SALT, + + /* + * Some information about the technical persons running this installation. + * The email address will be used as the recipient address for error reports, and + * also as the technical contact in generated metadata. + */ + 'technicalcontact_name' => $ADMIN_NAME, + 'technicalcontact_email' => $ADMIN_EMAIL, + + /* + * The timezone of the server. This option should be set to the timezone you want + * simpleSAMLphp to report the time in. The default is to guess the timezone based + * on your system timezone. + * + * See this page for a list of valid timezones: http://php.net/manual/en/timezones.php + */ + 'timezone' => $TIMEZONE, + + /* + * Logging. + * + * define the minimum log level to log + * SimpleSAML\Logger::ERR No statistics, only errors + * SimpleSAML\Logger::WARNING No statistics, only warnings/errors + * SimpleSAML\Logger::NOTICE Statistics and errors + * SimpleSAML\Logger::INFO Verbose logs + * SimpleSAML\Logger::DEBUG Full debug logs - not reccomended for production + * + * Choose logging handler. + * + * Options: [syslog,file,errorlog,stderr] + * + */ + 'logging.level' => $logLevels[$LOGGING_LEVEL], + 'logging.handler' => $LOGGING_HANDLER, + + /* + * Specify the format of the logs. Its use varies depending on the log handler used (for instance, you cannot + * control here how dates are displayed when using the syslog or errorlog handlers), but in general the options + * are: + * + * - %date{}: the date and time, with its format specified inside the brackets. See the PHP documentation + * of the strftime() function for more information on the format. If the brackets are omitted, the standard + * format is applied. This can be useful if you just want to control the placement of the date, but don't care + * about the format. + * + * - %process: the name of the SimpleSAMLphp process. Remember you can configure this in the 'logging.processname' + * option below. + * + * - %level: the log level (name or number depending on the handler used). + * + * - %stat: if the log entry is intended for statistical purposes, it will print the string 'STAT ' (bear in mind + * the trailing space). + * + * - %trackid: the track ID, an identifier that allows you to track a single session. + * + * - %srcip: the IP address of the client. If you are behind a proxy, make sure to modify the + * $_SERVER['REMOTE_ADDR'] variable on your code accordingly to the X-Forwarded-For header. + * + * - %msg: the message to be logged. + * + */ + //'logging.format' => '%date{%b %d %H:%M:%S} %process %level %stat[%trackid] %msg', + + /* + * Choose which facility should be used when logging with syslog. + * + * These can be used for filtering the syslog output from simpleSAMLphp into its + * own file by configuring the syslog daemon. + * + * See the documentation for openlog (http://php.net/manual/en/function.openlog.php) for available + * facilities. Note that only LOG_USER is valid on windows. + * + * The default is to use LOG_LOCAL5 if available, and fall back to LOG_USER if not. + */ + 'logging.facility' => defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER, + + /* + * The process name that should be used when logging to syslog. + * The value is also written out by the other logging handlers. + */ + 'logging.processname' => 'simplesamlphp', + + /* Logging: file - Logfilename in the loggingdir from above. + */ + 'logging.logfile' => 'simplesamlphp.log', + + /* (New) statistics output configuration. + * + * This is an array of outputs. Each output has at least a 'class' option, which + * selects the output. + */ + 'statistics.out' => [// Log statistics to the normal log. + /* + [ + 'class' => 'core:Log', + 'level' => 'notice', + ], + */ + // Log statistics to files in a directory. One file per day. + /* + [ + 'class' => 'core:File', + 'directory' => '/var/log/stats', + ], + */ + ], + + + /* + * Enable + * + * Which functionality in simpleSAMLphp do you want to enable. Normally you would enable only + * one of the functionalities below, but in some cases you could run multiple functionalities. + * In example when you are setting up a federation bridge. + */ + 'enable.saml20-idp' => $SAML20_IDP_ENABLE, + 'enable.shib13-idp' => false, + 'enable.adfs-idp' => false, + 'enable.wsfed-sp' => false, + 'enable.authmemcookie' => false, + + + /* + * Module enable configuration + * + * Configuration to override module enabling/disabling. + * + * Example: + * + * 'module.enable' => array( + * // Setting to TRUE enables. + * 'exampleauth' => TRUE, + * // Setting to FALSE disables. + * 'saml' => FALSE, + * // Unset or NULL uses default. + * 'core' => NULL, + * ), + * + */ + + 'module.enable' => [ + // Setting to TRUE enables. + 'authgoogle' => $GOOGLE_ENABLE, + 'expirychecker' => true, + 'material' => true, + 'mfa' => true, + 'profilereview' => true, + 'silauth' => true, + 'sildisco' => true, + ], + + /* + * This value is the duration of the session in seconds. Make sure that the time duration of + * cookies both at the SP and the IdP exceeds this duration. + */ + 'session.duration' => $SESSION_DURATION, + + /* + * Sets the duration, in seconds, data should be stored in the datastore. As the datastore is used for + * login and logout requests, thid option will control the maximum time these operations can take. + * The default is 4 hours (4*60*60) seconds, which should be more than enough for these operations. + */ + 'session.datastore.timeout' => $SESSION_DATASTORE_TIMEOUT, + + /* + * Sets the duration, in seconds, auth state should be stored. + */ + 'session.state.timeout' => $SESSION_STATE_TIMEOUT, + + /* + * Option to override the default settings for the session cookie name + */ + 'session.cookie.name' => 'SSPSESSID', + + /* + * Expiration time for the session cookie, in seconds. + * + * Defaults to 0, which means that the cookie expires when the browser is closed. + * + * Example: + * 'session.cookie.lifetime' => 30*60, + */ + 'session.cookie.lifetime' => $SESSION_COOKIE_LIFETIME, + + /* + * Limit the path of the cookies. + * + * Can be used to limit the path of the cookies to a specific subdirectory. + * + * Example: + * 'session.cookie.path' => '/simplesaml/', + */ + 'session.cookie.path' => '/', + + /* + * Cookie domain. + * + * Can be used to make the session cookie available to several domains. + * + * Example: + * 'session.cookie.domain' => '.example.org', + */ + 'session.cookie.domain' => null, + + /* + * Set the secure flag in the cookie. + * + * Set this to TRUE if the user only accesses your service + * through https. If the user can access the service through + * both http and https, this must be set to FALSE. + */ + 'session.cookie.secure' => $SECURE_COOKIE, + + /* + * When set to FALSE fallback to transient session on session initialization + * failure, throw exception otherwise. + */ + 'session.disable_fallback' => false, + + /* + * Enable secure POST from HTTPS to HTTP. + * + * If you have some SP's on HTTP and IdP is normally on HTTPS, this option + * enables secure POSTing to HTTP endpoint without warning from browser. + * + * For this to work, module.php/core/postredirect.php must be accessible + * also via HTTP on IdP, e.g. if your IdP is on + * https://idp.example.org/ssp/, then + * http://idp.example.org/ssp/module.php/core/postredirect.php must be accessible. + */ + 'enable.http_post' => false, + + /* + * Options to override the default settings for php sessions. + */ + 'session.phpsession.cookiename' => null, + 'session.phpsession.savepath' => null, + 'session.phpsession.httponly' => true, + + /* + * Option to override the default settings for the auth token cookie + */ + 'session.authtoken.cookiename' => 'SSPAUTHTOKEN', + + /* + * Options for remember me feature for IdP sessions. Remember me feature + * has to be also implemented in authentication source used. + * + * Option 'session.cookie.lifetime' should be set to zero (0), i.e. cookie + * expires on browser session if remember me is not checked. + * + * Session duration ('session.duration' option) should be set according to + * 'session.rememberme.lifetime' option. + * + * It's advised to use remember me feature with session checking function + * defined with 'session.check_function' option. + */ + 'session.rememberme.enable' => false, + 'session.rememberme.checked' => false, + 'session.rememberme.lifetime' => $SESSION_REMEMBERME_LIFETIME, + + /** + * Custom function for session checking called on session init and loading. + * See docs/simplesamlphp-advancedfeatures.txt for function code example. + * + * Example: + * 'session.check_function' => array('sspmod_example_Util', 'checkSession'), + */ + + /* + * Languages available, RTL languages, and what language is default + */ + 'language.available' => array( + 'en', 'es', 'fr', 'pt', + ), + 'language.rtl' => array('ar', 'dv', 'fa', 'ur', 'he'), + 'language.default' => 'en', + + /* + * Options to override the default settings for the language parameter + */ + 'language.parameter.name' => 'language', + 'language.parameter.setcookie' => true, + + /* + * Options to override the default settings for the language cookie + */ + 'language.cookie.name' => 'language', + 'language.cookie.domain' => null, + 'language.cookie.path' => '/', + 'language.cookie.lifetime' => (60 * 60 * 24 * 900), + + /** + * Custom getLanguage function called from SimpleSAML_XHTML_Template::getLanguage(). + * Function should return language code of one of the available languages or NULL. + * See SimpleSAML_XHTML_Template::getLanguage() source code for more info. + * + * This option can be used to implement a custom function for determining + * the default language for the user. + * + * Example: + * 'language.get_language_function' => array('sspmod_example_Template', 'getLanguage'), + */ + + /* + * Extra dictionary for attribute names. + * This can be used to define local attributes. + * + * The format of the parameter is a string with :. + * + * Specifying this option will cause us to look for modules//dictionaries/.definition.json + * The dictionary should look something like: + * + * { + * "firstattribute": { + * "en": "English name", + * "no": "Norwegian name" + * }, + * "secondattribute": { + * "en": "English name", + * "no": "Norwegian name" + * } + * } + * + * Note that all attribute names in the dictionary must in lowercase. + * + * Example: 'attributes.extradictionary' => 'ourmodule:ourattributes', + */ + 'attributes.extradictionary' => null, + + /* + * Which theme directory should be used? + */ + 'theme.use' => $THEME_USE, + + + /* + * Default IdP for WS-Fed. + */ + // 'default-wsfed-idp' => 'urn:federation:pingfederate:localhost', + + /* + * Whether the discovery service should allow the user to save his choice of IdP. + */ + 'idpdisco.enableremember' => true, + 'idpdisco.rememberchecked' => true, + + // Disco service only accepts entities it knows. + 'idpdisco.validate' => true, + + 'idpdisco.extDiscoveryStorage' => null, + + /* + * IdP Discovery service look configuration. + * Wether to display a list of idp or to display a dropdown box. For many IdP' a dropdown box + * gives the best use experience. + * + * When using dropdown box a cookie is used to highlight the previously chosen IdP in the dropdown. + * This makes it easier for the user to choose the IdP + * + * Options: [links,dropdown] + * + */ + 'idpdisco.layout' => 'links', + + /* + * Whether simpleSAMLphp should sign the response or the assertion in SAML 1.1 authentication + * responses. + * + * The default is to sign the assertion element, but that can be overridden by setting this + * option to TRUE. It can also be overridden on a pr. SP basis by adding an option with the + * same name to the metadata of the SP. + */ + 'shib13.signresponse' => true, + + + /* + * Authentication processing filters that will be executed for all IdPs + * Both Shibboleth and SAML 2.0 + */ + 'authproc.idp' => [ + /* Enable the authproc filter below to add URN Prefixces to all attributes + 10 => array( + 'class' => 'core:AttributeMap', 'addurnprefix' + ), */ + /* Enable the authproc filter below to automatically generated eduPersonTargetedID. + 20 => 'core:TargetedID', + */ + + // Adopts language from attribute to use in UI + 30 => 'core:LanguageAdaptor', + + /* Add a realm attribute from edupersonprincipalname + 40 => 'core:AttributeRealm', + */ + 45 => [ + 'class' => 'core:StatisticsWithAttribute', + 'attributename' => 'realm', + 'type' => 'saml20-idp-SSO', + ], + + // If no attributes are requested in the SP metadata, then these will be sent through + 50 => [ + 'class' => 'core:AttributeLimit', + 'default' => true, + 'eduPersonPrincipalName', 'sn', 'givenName', 'mail', + ], + + // Use the uid value to populate the nameid entry + 60 => [ + 'class' => 'saml:AttributeNameID', + 'attribute' => 'uid', + 'Format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', + ], + + /* + * Search attribute "distinguishedName" for pattern and replaces if found + + 70 => array( + 'class' => 'core:AttributeAlter', + 'pattern' => '/OU=studerende/', + 'replacement' => 'Student', + 'subject' => 'distinguishedName', + '%replace', + ), + */ + + /* + * Consent module is enabled (with no permanent storage, using cookies). + + 90 => array( + 'class' => 'consent:Consent', + 'store' => 'consent:Cookie', + 'focus' => 'yes', + 'checked' => TRUE + ), + */ + + + // If language is set in Consent module it will be added as an attribute. + 99 => 'core:LanguageAdaptor', + ], + /* + * Authentication processing filters that will be executed for all SPs + * Both Shibboleth and SAML 2.0 + */ + 'authproc.sp' => [ + /* + 10 => array( + 'class' => 'core:AttributeMap', 'removeurnprefix' + ), + */ + + /* + * Generate the 'group' attribute populated from other variables, including eduPersonAffiliation. + 60 => array( + 'class' => 'core:GenerateGroups', 'eduPersonAffiliation' + ), + */ + /* + * All users will be members of 'users' and 'members' + 61 => array( + 'class' => 'core:AttributeAdd', 'groups' => array('users', 'members') + ), + */ + + // Adopts language from attribute to use in UI + 90 => 'core:LanguageAdaptor', + + ], + + + /* + * This option configures the metadata sources. The metadata sources is given as an array with + * different metadata sources. When searching for metadata, simpleSAMPphp will search through + * the array from start to end. + * + * Each element in the array is an associative array which configures the metadata source. + * The type of the metadata source is given by the 'type' element. For each type we have + * different configuration options. + * + * Flat file metadata handler: + * - 'type': This is always 'flatfile'. + * - 'directory': The directory we will load the metadata files from. The default value for + * this option is the value of the 'metadatadir' configuration option, or + * 'metadata/' if that option is unset. + * + * XML metadata handler: + * This metadata handler parses an XML file with either an EntityDescriptor element or an + * EntitiesDescriptor element. The XML file may be stored locally, or (for debugging) on a remote + * web server. + * The XML hetadata handler defines the following options: + * - 'type': This is always 'xml'. + * - 'file': Path to the XML file with the metadata. + * - 'url': The URL to fetch metadata from. THIS IS ONLY FOR DEBUGGING - THERE IS NO CACHING OF THE RESPONSE. + * + * + * Examples: + * + * This example defines two flatfile sources. One is the default metadata directory, the other + * is a metadata directory with autogenerated metadata files. + * + * 'metadata.sources' => array( + * array('type' => 'flatfile'), + * array('type' => 'flatfile', 'directory' => 'metadata-generated'), + * ), + * + * This example defines a flatfile source and an XML source. + * 'metadata.sources' => array( + * array('type' => 'flatfile'), + * array('type' => 'xml', 'file' => 'idp.example.org-idpMeta.xml'), + * ), + * + * + * Default: + * 'metadata.sources' => array( + * array('type' => 'flatfile') + * ), + */ + 'metadata.sources' => [ + ['type' => 'flatfile'], + ], + + + /* + * Configure the datastore for simpleSAMLphp. + * + * - 'phpsession': Limited datastore, which uses the PHP session. + * - 'memcache': Key-value datastore, based on memcache. + * - 'sql': SQL datastore, using PDO. + * + * The default datastore is 'phpsession'. + * + * (This option replaces the old 'session.handler'-option.) + */ + 'store.type' => 'phpsession', + + + /* + * The DSN the sql datastore should connect to. + * + * See http://www.php.net/manual/en/pdo.drivers.php for the various + * syntaxes. + */ + 'store.sql.dsn' => 'sqlite:/path/to/sqlitedatabase.sq3', + + /* + * The username and password to use when connecting to the database. + */ + 'store.sql.username' => null, + 'store.sql.password' => null, + + /* + * The prefix we should use on our tables. + */ + 'store.sql.prefix' => 'simpleSAMLphp', + + + /* + * Configuration for the MemcacheStore class. This allows you to store + * multiple redudant copies of sessions on different memcache servers. + * + * 'memcache_store.servers' is an array of server groups. Every data + * item will be mirrored in every server group. + * + * Each server group is an array of servers. The data items will be + * load-balanced between all servers in each server group. + * + * Each server is an array of parameters for the server. The following + * options are available: + * - 'hostname': This is the hostname or ip address where the + * memcache server runs. This is the only required option. + * - 'port': This is the port number of the memcache server. If this + * option isn't set, then we will use the 'memcache.default_port' + * ini setting. This is 11211 by default. + * - 'weight': This sets the weight of this server in this server + * group. http://php.net/manual/en/function.Memcache-addServer.php + * contains more information about the weight option. + * - 'timeout': The timeout for this server. By default, the timeout + * is 3 seconds. + * + * Example of redudant configuration with load balancing: + * This configuration makes it possible to lose both servers in the + * a-group or both servers in the b-group without losing any sessions. + * Note that sessions will be lost if one server is lost from both the + * a-group and the b-group. + * + * 'memcache_store.servers' => array( + * array( + * array('hostname' => 'mc_a1'), + * array('hostname' => 'mc_a2'), + * ), + * array( + * array('hostname' => 'mc_b1'), + * array('hostname' => 'mc_b2'), + * ), + * ), + * + * Example of simple configuration with only one memcache server, + * running on the same computer as the web server: + * Note that all sessions will be lost if the memcache server crashes. + * + * 'memcache_store.servers' => array( + * array( + * array('hostname' => 'localhost'), + * ), + * ), + * + */ + 'memcache_store.servers' => [ + [ + ['hostname' => 'localhost'], + ], + ], + + + /* + * This value is the duration data should be stored in memcache. Data + * will be dropped from the memcache servers when this time expires. + * The time will be reset every time the data is written to the + * memcache servers. + * + * This value should always be larger than the 'session.duration' + * option. Not doing this may result in the session being deleted from + * the memcache servers while it is still in use. + * + * Set this value to 0 if you don't want data to expire. + * + * Note: The oldest data will always be deleted if the memcache server + * runs out of storage space. + */ + 'memcache_store.expires' => 36 * (60 * 60), // 36 hours. + + + /* + * Should signing of generated metadata be enabled by default. + * + * Metadata signing can also be enabled for a individual SP or IdP by setting the + * same option in the metadata for the SP or IdP. + */ + 'metadata.sign.enable' => true, + + /* + * The default key & certificate which should be used to sign generated metadata. These + * are files stored in the cert dir. + * These values can be overridden by the options with the same names in the SP or + * IdP metadata. + * + * If these aren't specified here or in the metadata for the SP or IdP, then + * the 'certificate' and 'privatekey' option in the metadata will be used. + * if those aren't set, signing of metadata will fail. + */ + 'metadata.sign.privatekey' => 'ssp-hub-sp3.pem', + 'metadata.sign.privatekey_pass' => null, + 'metadata.sign.certificate' => 'ssp-hub-sp3.crt', + + + /* + * Proxy to use for retrieving URLs. + * + * Example: + * 'proxy' => 'tcp://proxy.example.com:5100' + */ + 'proxy' => null, + + /* + * Array of domains that are allowed when generating links or redirections + * to URLs. simpleSAMLphp will use this option to determine whether to + * to consider a given URL valid or not, but you should always validate + * URLs obtained from the input on your own (i.e. ReturnTo or RelayState + * parameters obtained from the $_REQUEST array). + * + * Set to NULL to disable checking of URLs. + * + * simpleSAMLphp will automatically add your own domain (either by checking + * it dinamically, or by using the domain defined in the 'baseurlpath' + * directive, the latter having precedence) to the list of trusted domains, + * in case this option is NOT set to NULL. In that case, you are explicitly + * telling simpleSAMLphp to verify URLs. + * + * Set to an empty array to disallow ALL redirections or links pointing to + * an external URL other than your own domain. + * + * Example: + * 'trusted.url.domains' => array('sp.example.com', 'app.example.com'), + */ + 'trusted.url.domains' => null, + +]; diff --git a/development/sp3-local/metadata/saml20-idp-remote.php b/development/sp3-local/metadata/saml20-idp-remote.php new file mode 100644 index 00000000..f518f827 --- /dev/null +++ b/development/sp3-local/metadata/saml20-idp-remote.php @@ -0,0 +1,19 @@ + 'http://ssp-hub.local/saml2/idp/SSOService.php', + 'SingleLogoutService' => 'http://ssp-hub.local/saml2/idp/SingleLogoutService.php', + 'certData' =>'MIIDzzCCAregAwIBAgIJANuvVcQPANecMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEPMA0GA1UEBwwGV2F4aGF3MQwwCgYDVQQKDANTSUwxDTALBgNVBAsMBEdUSVMxDjAMBgNVBAMMBVN0ZXZlMSQwIgYJKoZIhvcNAQkBFhVzdGV2ZV9iYWd3ZWxsQHNpbC5vcmcwHhcNMTYxMDE3MTIzMTEyWhcNMjYxMDE3MTIzMTEyWjB+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMxDzANBgNVBAcMBldheGhhdzEMMAoGA1UECgwDU0lMMQ0wCwYDVQQLDARHVElTMQ4wDAYDVQQDDAVTdGV2ZTEkMCIGCSqGSIb3DQEJARYVc3RldmVfYmFnd2VsbEBzaWwub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxAimEkw4Teyf/gZelL7OuQYg/JbDIKHPXJhLPBm/HK6pM5ZZKydVXTdMgMqkl4xK+xZ2CnkozsUiMLhAuWBsX9Dcz1M4SkPRwk4puFhXzsp7fKIVP43zUhF7p2TmbernrrIQHjg6PuegKmCGyiKUpukcYvf2RXNwHwJx+Uq0zLP4PgBSrQ2t1eKZ1jQ+noBb1NqOuy969WRYmN4EmjXDuJB9d+b3GwtbZToWgiFxFjd/NN9BFJXZEaLzRj5LAq5bu2vPPDZDarHFMRUzVJ91eafoaz6zpR1iUGj9zR+y2sUPxD/fJMZ+4AHWA2LOrTBBIuuWbp96yvcJ4WjmlfhcFQIDAQABo1AwTjAdBgNVHQ4EFgQUkJFAMJdr2lXsuezS6pDXHnmJspMwHwYDVR0jBBgwFoAUkJFAMJdr2lXsuezS6pDXHnmJspMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAOEPbchaUr45L5i+ueookevsABYnltwJZ4rYJbF9VURPcEhB6JxTMZqb4s113ftHvVYfoAfLYZ9swETaHL+esx41yAebf0kWpQ3f63S5F2FcrTj+HP0XsvW/EDrvaTKM9jnKPNmbXrpq06eaUZfkVL0TAUsxYTKkttTSTiESEzp5wzYyhp7l3kpHhEvGOlh5suYjnZ2HN0uxscCR6PS47H6TMMEZuG032DWDC016/JniWvERtpf4Yw26V+I9xevp2E2MPcZne31Pe3sCh4Wpe4cV/SCFqZHlpnH96ncz4F+KvmmhbEx5VPhQSJNFIWEvI86k+lTNQOqj6YVvGvq95LQ==', + +]; + diff --git a/development/ssp/run-debug.sh b/development/ssp/run-debug.sh deleted file mode 100755 index 2cc4ea19..00000000 --- a/development/ssp/run-debug.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -echo "Installing php-xdebug" -apt-get update -y -apt-get install -y php-xdebug -phpenmod xdebug - -INI_FILE="/etc/php/7.0/apache2/php.ini" -echo "Configuring debugger in $INI_FILE" -echo "xdebug.remote_enable=1" >> $INI_FILE -echo "xdebug.remote_host=$XDEBUG_REMOTE_HOST" >> $INI_FILE - -# now the builtin run script can be started -/data/run.sh diff --git a/docker-compose.yml b/docker-compose.yml index a374ffdf..267dbfa8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,4 @@ -version: "2" services: - ssp: - build: . - volumes: - # Utilize custom certs - - ./development/ssp/cert:/data/vendor/simplesamlphp/simplesamlphp/cert - - # Utilize custom configs - - ./development/ssp/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php - - # Configure the debugger - - ./development/ssp/run-debug.sh:/data/run-debug.sh - command: ["/data/run-debug.sh"] - ports: - - "80:80" - env_file: - - ./local.env - db: image: mariadb:10 ports: @@ -27,15 +9,38 @@ services: MYSQL_USER: silauth MYSQL_PASSWORD: silauth + dbadmin: + image: phpmyadmin/phpmyadmin + ports: + - "8080:80" + environment: + PMA_HOST: db + PMA_USER: silauth + PMA_PASSWORD: silauth + test: build: . depends_on: - ssp-hub.local - ssp-idp1.local + - ssp-idp2.local + - ssp-idp3.local - ssp-sp1.local + - ssp-sp2.local + - ssp-sp3.local + - pwmanager.local - test-browser environment: - - COMPOSER_CACHE_DIR=/composer + MYSQL_HOST: db + MYSQL_DATABASE: silauth + MYSQL_USER: silauth + MYSQL_PASSWORD: silauth + COMPOSER_CACHE_DIR: /composer + PROFILE_URL_FOR_TESTS: http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub + ADMIN_EMAIL: john_doe@there.com + ADMIN_PASS: b + SECRET_SALT: abc123 + IDP_NAME: x volumes: - ./composer.json:/data/composer.json - ./composer.lock:/data/composer.lock @@ -45,7 +50,14 @@ services: - ./dockerbuild/run-tests.sh:/data/run-tests.sh - ./dockerbuild/apply-dictionaries-overrides.php:/data/apply-dictionaries-overrides.php - ./features:/data/features + - ./behat.yml:/data/behat.yml - ./tests:/data/tests + - ./modules/mfa:/data/vendor/simplesamlphp/simplesamlphp/modules/mfa + - ./modules/expirychecker:/data/vendor/simplesamlphp/simplesamlphp/modules/expirychecker + - ./modules/profilereview:/data/vendor/simplesamlphp/simplesamlphp/modules/profilereview + - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth + - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco + - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material command: ["/data/run-tests.sh"] test-browser: @@ -67,10 +79,17 @@ services: env_file: - ./local.env environment: - - COMPOSER_CACHE_DIR=/composer + COMPOSER_CACHE_DIR: /composer ssp-hub.local: build: . + depends_on: + - ssp-idp1.local + - ssp-idp2.local + - ssp-idp3.local + - ssp-sp1.local + - ssp-sp2.local + - ssp-sp3.local volumes: # Utilize custom certs - ./development/hub/cert:/data/vendor/simplesamlphp/simplesamlphp/cert @@ -89,6 +108,14 @@ services: # Enable checking our test metadata - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh + + # Local modules + - ./modules/mfa:/data/vendor/simplesamlphp/simplesamlphp/modules/mfa + - ./modules/expirychecker:/data/vendor/simplesamlphp/simplesamlphp/modules/expirychecker + - ./modules/profilereview:/data/vendor/simplesamlphp/simplesamlphp/modules/profilereview + - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth + - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco + - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material command: /data/run-debug.sh ports: - "80:80" @@ -120,12 +147,25 @@ services: - ./development/idp-local/metadata/saml20-idp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-hosted.php - ./development/idp-local/metadata/saml20-sp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-sp-remote.php - # Misc. files needed - - ./development/enable-exampleauth-module.sh:/data/enable-exampleauth-module.sh + # Customized SSP code -- TODO: make a better solution that doesn't require hacking SSP code + - ./development/UserPass.php:/data/vendor/simplesamlphp/simplesamlphp/modules/exampleauth/lib/Auth/Source/UserPass.php # Enable checking our test metadata - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh - command: 'bash -c "/data/enable-exampleauth-module.sh && /data/run.sh"' + + # Include the features folder (for the FakeIdBrokerClient class) + - ./features:/data/features + + # Local modules + - ./modules/mfa:/data/vendor/simplesamlphp/simplesamlphp/modules/mfa + - ./modules/expirychecker:/data/vendor/simplesamlphp/simplesamlphp/modules/expirychecker + - ./modules/profilereview:/data/vendor/simplesamlphp/simplesamlphp/modules/profilereview + - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth + - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco + - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material + command: > + bash -c "whenavail db 3306 60 /data/vendor/simplesamlphp/simplesamlphp/modules/silauth/lib/Auth/Source/yii migrate --interactive=0 && + /data/run.sh" ports: - "8085:80" environment: @@ -133,16 +173,26 @@ services: ADMIN_PASS: "a" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "IDP 1" + IDP_DOMAIN_NAME: "mfaidp" + ID_BROKER_ACCESS_TOKEN: "dummy" + ID_BROKER_ASSERT_VALID_IP: "false" + ID_BROKER_BASE_URI: "dummy" + ID_BROKER_TRUSTED_IP_RANGES: "192.168.0.1/8" + MFA_SETUP_URL: "http://pwmanager.local:8084/module.php/core/authenticate.php?as=ssp-hub-custom-port" + REMEMBER_ME_SECRET: "12345" + PROFILE_URL: "http://pwmanager.local:8084/module.php/core/authenticate.php?as=ssp-hub-custom-port" + PROFILE_URL_FOR_TESTS: "http://pwmanager.local/module.php/core/authenticate.php?as=ssp-hub" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" - THEME_USE: "material:material" + THEME_USE: "default" SESSION_STORE_TYPE: "sql" MYSQL_HOST: "db" MYSQL_DATABASE: "silauth" MYSQL_USER: "silauth" MYSQL_PASSWORD: "silauth" + BASE_URL_PATH: "http://ssp-idp1.local/" - idp2: + ssp-idp2.local: build: . volumes: # Utilize custom certs @@ -155,7 +205,17 @@ services: # Utilize custom metadata - ./development/idp2-local/metadata/saml20-idp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-hosted.php - ./development/idp2-local/metadata/saml20-sp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-sp-remote.php - command: /data/run.sh + + # Customized SSP code -- TODO: make a better solution that doesn't require hacking SSP code + - ./development/UserPass.php:/data/vendor/simplesamlphp/simplesamlphp/modules/exampleauth/lib/Auth/Source/UserPass.php + + # Local modules + - ./modules/mfa:/data/vendor/simplesamlphp/simplesamlphp/modules/mfa + - ./modules/expirychecker:/data/vendor/simplesamlphp/simplesamlphp/modules/expirychecker + - ./modules/profilereview:/data/vendor/simplesamlphp/simplesamlphp/modules/profilereview + - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth + - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco + - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material ports: - "8086:80" environment: @@ -165,6 +225,40 @@ services: IDP_NAME: "IDP 2" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" + THEME_USE: "material:material" + + ssp-idp3.local: + build: . + volumes: + # Utilize custom certs + - ./development/idp3-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert + + # Utilize custom configs + - ./development/idp3-local/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + - ./development/idp3-local/config/config.php:/data/vendor/simplesamlphp/simplesamlphp/config/config.php + + # Utilize custom metadata + - ./development/idp3-local/metadata/saml20-idp-hosted.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-hosted.php + - ./development/idp3-local/metadata/saml20-sp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-sp-remote.php + + # Local modules + - ./modules/mfa:/data/vendor/simplesamlphp/simplesamlphp/modules/mfa + - ./modules/expirychecker:/data/vendor/simplesamlphp/simplesamlphp/modules/expirychecker + - ./modules/profilereview:/data/vendor/simplesamlphp/simplesamlphp/modules/profilereview + - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth + - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco + - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material + ports: + - "8087:80" + env_file: + - local.env + environment: + ADMIN_EMAIL: "john_doe@there.com" + ADMIN_PASS: "c" + SECRET_SALT: "h57fjem34fh*nsJFGNjweJ" + SECURE_COOKIE: "false" + SHOW_SAML_ERRORS: "true" + IDP_NAME: "IdP3" ssp-sp1.local: build: . @@ -181,6 +275,14 @@ services: # Enable checking our test metadata - ./dockerbuild/run-metadata-tests.sh:/data/run-metadata-tests.sh + + # Local modules + - ./modules/mfa:/data/vendor/simplesamlphp/simplesamlphp/modules/mfa + - ./modules/expirychecker:/data/vendor/simplesamlphp/simplesamlphp/modules/expirychecker + - ./modules/profilereview:/data/vendor/simplesamlphp/simplesamlphp/modules/profilereview + - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth + - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco + - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material ports: - "8081:80" environment: @@ -192,7 +294,7 @@ services: SAML20_IDP_ENABLE: "false" ADMIN_PROTECT_INDEX_PAGE: "false" - sp2: + ssp-sp2.local: build: . volumes: # Utilize custom certs @@ -204,6 +306,14 @@ services: # Utilize custom metadata - ./development/sp2-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php + + # Local modules + - ./modules/mfa:/data/vendor/simplesamlphp/simplesamlphp/modules/mfa + - ./modules/expirychecker:/data/vendor/simplesamlphp/simplesamlphp/modules/expirychecker + - ./modules/profilereview:/data/vendor/simplesamlphp/simplesamlphp/modules/profilereview + - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth + - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco + - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material ports: - "8082:80" environment: @@ -214,7 +324,124 @@ services: SHOW_SAML_ERRORS: "true" SAML20_IDP_ENABLE: "false" ADMIN_PROTECT_INDEX_PAGE: "false" - + + ssp-sp3.local: + build: . + volumes: + # Utilize custom certs + - ./development/sp3-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert + + # Utilize custom configs + - ./development/sp3-local/config/config.php:/data/vendor/simplesamlphp/simplesamlphp/config/config.php + - ./development/sp3-local/config/authsources.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + + # Utilize custom metadata + - ./development/sp3-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php + ports: + - "8083:80" + env_file: + - local.env + environment: + ADMIN_EMAIL: john_doe@there.com + ADMIN_PASS: sp3 + SECRET_SALT: h57fjemb&dn^nsJFGNjweJz3 + SECURE_COOKIE: "false" + SHOW_SAML_ERRORS: "true" + SAML20_IDP_ENABLE: "false" + ADMIN_PROTECT_INDEX_PAGE: "false" + + pwmanager.local: + image: silintl/ssp-base:develop + volumes: + # Utilize custom certs + - ./development/sp-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert + + # Utilize custom configs + - ./development/sp-local/config/authsources-pwmanager.php:/data/vendor/simplesamlphp/simplesamlphp/config/authsources.php + + # Utilize custom metadata + - ./development/sp-local/metadata/saml20-idp-remote.php:/data/vendor/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php + ports: + - "8084:80" + environment: + ADMIN_EMAIL: john_doe@there.com + ADMIN_PASS: sp1 + IDP_NAME: THIS VARIABLE IS REQUIRED BUT PROBABLY NOT USED + SECRET_SALT: NOT-a-secret-k49fjfkw73hjf9t87wjiw + SECURE_COOKIE: "false" + SHOW_SAML_ERRORS: "true" + SAML20_IDP_ENABLE: "false" + ADMIN_PROTECT_INDEX_PAGE: "false" + THEME_USE: material:material + + # the broker and brokerDb containers are used by the silauth module + broker: + image: silintl/idp-id-broker:develop + ports: + - "80" + depends_on: + - brokerDb + env_file: + - ./local.broker.env + environment: + IDP_NAME: "idp" + MYSQL_HOST: "brokerDb" + MYSQL_DATABASE: "broker" + MYSQL_USER: "user" + MYSQL_PASSWORD: "pass" + EMAIL_SERVICE_accessToken: "dummy" + EMAIL_SERVICE_assertValidIp: "false" + EMAIL_SERVICE_baseUrl: "dummy" + EMAILER_CLASS: Sil\SilIdBroker\Behat\Context\fakes\FakeEmailer + HELP_CENTER_URL: "https://example.org/help" + PASSWORD_FORGOT_URL: "https://example.org/forgot" + PASSWORD_PROFILE_URL: "https://example.org/profile" + SUPPORT_EMAIL: "support@example.org" + EMAIL_SIGNATURE: "one red pill, please" + API_ACCESS_KEYS: "test-cli-abc123" + APP_ENV: "dev" + command: ["bash", "-c", "whenavail brokerDb 3306 60 ./yii migrate --interactive=0 && ./run.sh"] + + brokerDb: + image: mariadb:10 + ports: + - "3306" + environment: + MYSQL_ROOT_PASSWORD: "r00tp@ss!" + MYSQL_DATABASE: "broker" + MYSQL_USER: "user" + MYSQL_PASSWORD: "pass" + + dynamo: + image: cnadiminti/dynamodb-local + command: "-sharedDb -inMemory" + hostname: dynamo + ports: + - "8000:8000" + environment: + reschedule: on-node-failure + + init-dynamo: + image: garland/aws-cli-docker + command: "/init-dynamodb.sh" + volumes: + - ./development/init-dynamodb.sh:/init-dynamodb.sh + depends_on: + - dynamo + environment: + AWS_ACCESS_KEY_ID: 0 + AWS_SECRET_ACCESS_KEY: 0 + AWS_DEFAULT_REGION: us-east-1 + AWS_DYNAMODB_ENDPOINT: http://dynamo:8000 + + node: + image: node:lts-alpine + volumes: + - ./package.json:/data/package.json + - ./package-lock.json:/data/package-lock.json + - ./node_modules:/data/node_modules + working_dir: /data + networks: default: driver: bridge diff --git a/dockerbuild/config/authsources.php b/dockerbuild/config/authsources.php new file mode 100644 index 00000000..21dc6df7 --- /dev/null +++ b/dockerbuild/config/authsources.php @@ -0,0 +1,17 @@ + [ + // The default is to use core:AdminPassword, but it can be replaced with + // any authentication source. + + 'core:AdminPassword', + ], + + // Use SilAuth + 'silauth' => ConfigManager::getSspConfig(), +]; diff --git a/dockerbuild/ssp-overrides/config.php b/dockerbuild/config/config.php similarity index 100% rename from dockerbuild/ssp-overrides/config.php rename to dockerbuild/config/config.php diff --git a/dockerbuild/run-idp.sh b/dockerbuild/run-idp.sh index 11490e6d..d08922d0 100755 --- a/dockerbuild/run-idp.sh +++ b/dockerbuild/run-idp.sh @@ -1,17 +1,16 @@ #!/usr/bin/env bash -# Try to run database migrations -cd /data/vendor/simplesamlphp/simplesamlphp/modules/silauth -chmod a+x ./src/yii +# echo script commands to stdout +set -x + +# exit if any command fails +set -e -output=$(./src/yii migrate --interactive=0 2>&1) +# Try to run database migrations +cd /data/vendor/simplesamlphp/simplesamlphp/modules/silauth/lib/Auth/Source +chmod a+x ./yii -# If they failed, exit. -rc=$?; -if [[ $rc != 0 ]]; then - logger --priority user.err --stderr "Migrations failed with status ${rc} and output: ${output}" - exit $rc; -fi +./yii migrate --interactive=0 cd /data -./run.sh \ No newline at end of file +./run.sh diff --git a/dockerbuild/run-integration-tests.sh b/dockerbuild/run-integration-tests.sh index d57f8b27..b3d92545 100755 --- a/dockerbuild/run-integration-tests.sh +++ b/dockerbuild/run-integration-tests.sh @@ -1,18 +1,18 @@ #!/usr/bin/env bash -set -e +# echo script commands to stdout set -x +# exit if any command fails +set -e + cd /data export COMPOSER_ALLOW_SUPERUSER=1; composer install -whenavail "ssp-hub.local" 80 10 echo Hub ready -whenavail "ssp-idp1.local" 80 10 echo IDP 1 ready -whenavail "ssp-sp1.local" 80 10 echo SP 1 ready +whenavail "ssp-hub.local" 80 15 echo Hub ready +whenavail "ssp-idp1.local" 80 5 echo IDP 1 ready +whenavail "ssp-sp1.local" 80 5 echo SP 1 ready ./vendor/bin/behat \ - --append-snippets \ - --snippets-for=FeatureContext \ --no-interaction \ - --stop-on-failure #\ - #--strict + --stop-on-failure diff --git a/dockerbuild/run-metadata-tests.sh b/dockerbuild/run-metadata-tests.sh index b5873cb8..f052741c 100755 --- a/dockerbuild/run-metadata-tests.sh +++ b/dockerbuild/run-metadata-tests.sh @@ -1,9 +1,12 @@ #!/usr/bin/env bash -set -e +# echo script commands to stdout set -x +# exit if any command fails +set -e + cd /data export COMPOSER_ALLOW_SUPERUSER=1; composer install -./vendor/bin/phpunit -v tests/ +./vendor/bin/phpunit -v tests/MetadataTest.php diff --git a/dockerbuild/run-tests.sh b/dockerbuild/run-tests.sh index 69884f28..37c21ec7 100755 --- a/dockerbuild/run-tests.sh +++ b/dockerbuild/run-tests.sh @@ -1,7 +1,14 @@ #!/usr/bin/env bash -set -e +# echo script commands to stdout set -x +# exit if any command fails +set -e + /data/run-metadata-tests.sh + +./vendor/bin/phpunit -v tests/AnnouncementTest.php +./vendor/bin/phpunit -v tests/IdpDiscoTest.php + /data/run-integration-tests.sh diff --git a/dockerbuild/run.sh b/dockerbuild/run.sh index 748fc0b6..4d7ff730 100755 --- a/dockerbuild/run.sh +++ b/dockerbuild/run.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +# echo script commands to stdout +set -x + +# exit if any command fails +set -e + # This is a temporary fix (bug workaround) until ssp 2.0 is in use sed -i 's_\(\\SimpleSAML\\Error\\Assertion::installHandler()\)_// \1 _' /data/vendor/simplesamlphp/simplesamlphp/www/_include.php diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 00000000..e7f047c9 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,18 @@ +Four SPs, a hub (a combined IdP and SP) and three IdPs get spun up by docker-compose. In order for this to work, you will need to edit your hosts file to include entries for the following domains ... +* ssp-sp1.local # to be used with port 8081 +* ssp-sp2.local # to be used with port 8082 +* ssp-sp3.local # to be used with port 8083 +* pwmanager.local # to be used with port 8084 +* ssp-hub.local +* ssp-idp1.local # to be used with port 8085 +* ssp-idp2.local # to be used with port 8086 +* ssp-idp3.local # to be used with port 8087 + +The ./development folder holds various files needed by these containers. It's the ssp-hub.local container which is the focus and serves as the SimpleSAMLphp hub. + +### Who should see what? +* `ssp-sp1.local` should be able to see and authenticate through both `ssp-idp1.local` and `ssp-idp2.local` +* `ssp-sp2.local` should only be able to see and authenticate through `ssp-idp2.local` +* `ssp-sp3.local` should only be able to see and authenticate through `ssp-idp1.local` + +If a session authenticated through one of the IdP's that is not permitted for a certain SP, then the hub should force that SP to re-authenticate against the right IdP. diff --git a/docs/editing_authprocs.md b/docs/editing_authprocs.md new file mode 100644 index 00000000..42a62368 --- /dev/null +++ b/docs/editing_authprocs.md @@ -0,0 +1,40 @@ +The sildisco module includes a few Auth Procs that can be called from the `config.php` file or **SP or IdP metadata**. + +### AttributeMap.php + +Copies (rather than replaces) attributes according to an attribute map. + +### TagGroup.php + +Grabs the values of the `urn:oid:2.5.4.31` (member of) attribute and prepends them with `idp||`. +The idp's name value is taken from the saml20-idp-remote.php file. In particular, if the IdP's metadata entry includes a `'IDPNamespace'` value, that is used. Otherwise, if it includes a `'name'` value, that is used. Otherwise, it uses the entity id of the IdP. + +### AddIdp2NameId.php + +Grabs the value of the saml:sp:NameID and appends `@` to it. +The IdP's metadata needs to include an `'IDPNamespace'` entry with a string value that is alphanumeric with hyphens and underscores. + +In order for this to work, the SP needs to include a line in its authsources.php file in the Hub's entry ... + +` 'NameIDPolicy' => "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",` + +In addition, the IDP's sp-remote metadata stanza for the Hub needs to include ... + +` 'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',` + +### TrackIdps.php + +Creates and/or appends to a session value ("sildisco:authentication", "authenticated_idps") the **entity id** of the latest **IdP** to be used for authentication. + +### LogUser.php + +Logs information (common name, eduPrincipalPersonalName, employee number, IdP, SP, time) about each successful login to an AWS Dynamodb table. +``` + 97 => [ + 'class' =>'sildisco:LogUser', + 'DynamoRegion' => 'us-east-1', + 'DynamoLogTable' => 'sildisco_prod_user-log', + ], +``` +The following config is not needed on AWS, but it is needed locally +'DynamoEndpoint' ex. http://dynamo:8000 diff --git a/docs/functional_testing.md b/docs/functional_testing.md new file mode 100644 index 00000000..651e216b --- /dev/null +++ b/docs/functional_testing.md @@ -0,0 +1,129 @@ +# Automated Testing + +This is done through behat acceptance tests + +Once your containers are up, in your VM run ... + +`> docker-compose run --rm test /data/run-integration-tests.sh` + +Or, if you need to run just one of the tests, run ... + +`> docker-compose run --rm test bash` + +then + +`$ vendor/bin/behat features/mfa.feature:7` + +The tests are found in `/features`. They are similar to the manual tests listed below. + +# Manual Testing +## Main SP authenticates through Main Idp. Third SP is also authenticated. Second SP must re-authenticate. +### Ensure main SP goes to discovery page and can login through the main IdP +* Kill all your cookies for ssp\* +* Browse to http://ssp-sp1.local:8081/module.php/core/authenticate.php +* Click on ssp-hub +* This should redirect to http://ssp-hub.local/module.php/sildisco/disco.php?entityID=ssp-hub.local&... +* Select IdP 1 +* This should redirect to http://ssp-idp1.local:8085/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "a" as the password (without the quotation marks). +* This should return you to the main SP at http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +### Ensure third SP is also authenticated +* Browse to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub +* This should get you to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +### Ensure second SP is forced to authenticate +* Browse to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub +* This should redirect to http://ssp-idp2.local:8086/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "b" as the password. +* This should get you to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes (but there are none). + +### Ensure third SP is still authenticated +* Browse to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub +* This should get you to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +## Second SP authenticates through Second Idp. Main SP is forced to discovery page but is also authenticated. Third SP must re-authenticate. +### Ensure second SP can login through the second IdP +* Kill all your cookies for ssp\* +* Browse to http://ssp-sp2.local:8082/module.php/core/authenticate.php +* Click on ssp-hub +* This should redirect to http://ssp-idp2.local:8086/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "b" as the password. +* This should get you to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes (but there are none). + +### Ensure main SP goes to discovery page but is authenticated +* Browse to http://ssp-sp1.local:8081/module.php/core/authenticate.php +* Click on ssp-hub +* This should redirect to http://ssp-hub.local/module.php/sildisco/disco.php?entityID=ssp-hub.local&... +* Select IdP 2 +* This should return you to the main SP at http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes (but there are none). + +### Ensure third SP is forced to authenticate +* Browse to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub +* This should redirect to http://ssp-idp1.local:8085/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "a" as the password. +* This should get you to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +## Third SP authenticates through Main Idp. Main SP is forced to discovery page but is also authenticated. Second SP must re-authenticate. +### Ensure third SP can login through the main IdP +* Kill all your cookies for ssp\* +* Browse to http://ssp-sp3.local:8083/module.php/core/authenticate.php +* Click on ssp-hub +* This should redirect to http://ssp-idp1.local:8085/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "a" as the password. +* This should get you to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +### Ensure main SP goes to discovery page but is authenticated +* Browse to http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub +* This should redirect to http://ssp-hub.local/module.php/sildisco/disco.php?entityID=ssp-hub.local&... +* Select IdP 1 +* This should get you to http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +### Ensure second SP is forced to authenticate +* Browse to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub +* This should redirect to http://ssp-idp2.local:8086/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "b" as the password. +* This should get you to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes (but there are none). + +## Main SP authenticates through Second Idp. Second SP is also authenticated. Third SP must re-authenticate. +### Ensure main SP goes to discovery page and can login through the second IdP +* Kill all your cookies for ssp\* +* Browse to http://ssp-sp1.local:8081/module.php/core/authenticate.php +* Click on ssp-hub +* This should redirect to http://ssp-hub.local/module.php/sildisco/disco.php?entityID=ssp-hub.local&... +* Select IdP 2 +* This should redirect to http://ssp-idp2.local:8086/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "b" as the password +* This should return you to the main SP at http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes (but there are none). + +### Ensure second SP is also authenticated +* Browse to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub +* This should get you to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +### Ensure third SP is forced to authenticate +* Browse to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub +* This should redirect to http://ssp-idp1.local:8085/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "a" as the password. +* This should get you to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +### Ensure second SP is still authenticated +* Browse to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub +* This should get you to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. + +## Second SP authenticates through Second Idp. Main SP is forced to discovery page, chooses main IdP and must authenticate. +### Ensure second SP can login through the second IdP +* Kill all your cookies for ssp\* +* Browse to http://ssp-sp2.local:8082/module.php/core/authenticate.php +* Click on ssp-hub +* This should redirect to http://ssp-idp2.local:8086/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "b" as the password. +* This should get you to http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes (but there are none). + +### Ensure main SP goes to discovery page and must authenticate when choosing the main Idp +* Browse to http://ssp-sp1.local:8081/module.php/core/authenticate.php +* Click on ssp-hub +* This should redirect to http://ssp-hub.local/module.php/sildisco/disco.php?entityID=ssp-hub.local&... +* Select IdP 1 +* This should redirect to http://ssp-idp1.local:8085/module.php/core/loginuserpass.php?AuthState=... +* Login as admin using "a" as the password. +* This should get you to http://ssp-sp3.local:8083/module.php/core/authenticate.php?as=ssp-hub and show your saml attributes. diff --git a/docs/material_tests.md b/docs/material_tests.md new file mode 100644 index 00000000..5e287bd1 --- /dev/null +++ b/docs/material_tests.md @@ -0,0 +1,196 @@ + +# Testing the Material Module theme + +## Setup + +See [Local Testing](../README.md#local-testing) for instructions to set up your local development environment. + +## Hub page + +1. Goto [Hub 1](http://ssp-hub.local/module.php/core/authenticate.php?as=hub-discovery) + +## Error page + +1. Goto [Hub 1](http://ssp-hub.local) +1. Click **Federation** tab +1. Click either **Show metadata** link +1. Login as hub administrator: `username=`**admin** `password=`**abc123** + +## Logout page + +1. Goto [Hub 1](http://ssp-hub.local) +1. Click **Authentication** tab +1. Click **Test configured authentication sources** +1. Click **admin** +1. Login as hub administrator: `username=`**admin** `password=`**abc123** +1. Click **Logout** + +## Login page + +### Without theme in place + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp1** (first one) +1. login page should **NOT** have material design + +### With theme in place + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp2** (second one) +1. login page **SHOULD** have material design + +## Forgot password functionality + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp2** (second one) +1. Forgot password link should be visible + +## Helpful links functionality + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Help link should be visible under login form +1. Profile link should be visible under login form + +## Expiry functionality + +### About to expire page (expires in one day) + +_Note: This nag only works once since choosing later will simply set the nag date into the future a little. +If needed, use a new private/incognito browser window to retry.__ + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp2** (second one) +1. Login as an "about to expire" user: `username=`**next_day** `password=`**a** +1. Click **Later** +1. Click **Logout** + +### About to expire page (expires in three days) + +_Note: This nag only works once since choosing later will simply set the nag date into the future a little. +If needed, use a new private/incognito browser window to retry.__ + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp2** (second one) +1. Login as an "about to expire" user: `username=`**near_future** `password=`**a** +1. Click **Later** +1. Click **Logout** + +### Expired page + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp2** (second one) +1. Login as an "expired" user: `username=`**already_past** `password=`**a** + +## Multi-factor authentication (MFA) functionality + +### Nag about missing MFA setup + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as an "unprotected" user: `username=`**nag_for_mfa** `password=`**a** +1. The "learn more" link should be visible +1. Click **Enable** +1. Click your browser's back button +1. Click **Remind me later** +1. Click **Logout** + +### Nag about missing password recovery methods + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a user without any methods: `username=`**nag_for_method** `password=`**a** +1. Enter one of the following codes to verify (`94923279, 82743523, 77802769, 01970541, 37771076`) +1. Click **Add** +1. Click your browser's back button +1. Click **Remind me later** +1. Click **Logout** + +### Force MFA setup + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as an "unsafe" user: `username=`**must_set_up_mfa** `password=`**a** + +### Backup code + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a "backup code" user: `username=`**has_backupcode** `password=`**a** +1. Enter one of the following codes to verify (`94923279, 82743523, 77802769, 01970541, 37771076`) +1. Click **Logout** +1. In order to see the "running low on codes" page, simply log back in and use another code. +1. In order to see the "out of codes" page, simply log back in and out repeatedly until there are no more codes. + +### TOTP code + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a "totp" user: `username=`**has_totp** `password=`**a** +1. You should see the form to enter a totp code. +1. Set up an app using this secret, `JVRXKYTMPBEVKXLS` +1. Enter code from app to verify +1. Click **Logout** + +### Key (U2F) + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a "u2f" user: `username=`**has_u2f** `password=`**a** +1. Insert key and press +1. Click **Logout** + +### Key (WebAuthn) + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a "webauthn" user: `username=`**has_webauthn** `password=`**a** +1. Insert key and press +1. Click **Logout** + +### Multiple options + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a "multiple option" user: `username=`**has_all** `password=`**a** +1. Click **MORE OPTIONS** + +### Multiple options (legacy, with U2F) + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a "multiple option" user: `username=`**has_all_legacy** `password=`**a** +1. Click **MORE OPTIONS** + +### Manager rescue + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a "multiple option" user: `username=`**has_all** `password=`**a** +1. Click **MORE OPTIONS** +1. Click the help option +1. Choose **Send** + +_NOTE: At this time, the correct code is not known and can't be tested locally (it's only available in an email to the manager)_ + +## Announcements functionality + +1. Goto [SP 2](http://ssp-sp2.local:8082/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. The announcement should be displayed on the hub +1. Click **idp3** (first one) +1. The announcement should be displayed at the login screen + +## SP name functionality + +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. The sp name should appear in the banner + +## Profile review functionality +1. Goto [SP 1](http://ssp-sp1.local:8081/module.php/core/authenticate.php?as=ssp-hub-custom-port) +1. Click **idp4** (third one) +1. Login as a "Review needed" user: `username=`**needs_review** `password=`**a** +1. Enter one of the following printable codes to verify (`94923279, 82743523, 77802769, 01970541, 37771076`) +1. Click the button to update the profile +1. Click the button to continue +1. Click **Logout** + diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 00000000..8e36676b --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,4 @@ +The sildisco module is a module for simplesamlphp. It's main purpose is to allow a simplesamlphp hub to control which Service Providers (SP's) can see and authenticate through which Identity Providers (IdP's). + +It relies on some utilities found in [ssp-utilities](https://github.com/silinternational/ssp-utilities). + diff --git a/docs/the_hub.md b/docs/the_hub.md new file mode 100644 index 00000000..266826f7 --- /dev/null +++ b/docs/the_hub.md @@ -0,0 +1,40 @@ +The hub will need its certs, `config.php` and `authsources.php` files as a normal simplesamlphp installation. Examples of these can be found in the `./development/hub` folder. (Note the `discoURL` entry in the `authsources.php` file.) + +Other files it will need are as follows ... +* The files in the `./lib` folder will need to go into `/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco/lib` +* The files in the `./www` folder will need to go into `/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco/www` +* The `./sspoverrides/www_saml2_idp/SSOService.php` file will need overwrite the same out-of-the-box file in `/data/vendor/simplesamlphp/simplesamlphp/www/saml2/idp/` + +### Metadata files +The hub should use the `saml20-*-remote.php` files from [ssp-base](https://github.com/silinternational/ssp-base) in `/data/vendor/simplesamlphp/simplesamlphp/metadata/`. These pull in metadata from all the files named `idp-*.php` and `sp-*.php` respectively, including those in sub-folders. + +In order for forced re-authentication to be limited only to situations which warrant it, the `saml20-idp-hosted.php` file should include an authproc as such ... +> [ +> 'class' =>'sildisco:TrackIdps', +> ] + +#### IDP Remote metadata + +##### IDPNamespace +Each metadata stanza should include an `IDPNamespace` entry that includes no special characters. This is intended for namespacing the `NameId` value in the Auth Proc `AddIdp2NameId.php`. +It is also used by the `TagGroup.php` Auth Proc to convert group names into the form ... + +`idp||`. + +##### betaEnabled +An optional metadata entry is `betaEnabled`. +This will allow the IdP to be marked as `'enable' => true` when the user has a certain cookie ('beta_tester') that they would get from visiting `hub_domain/module.php/sildisco/betatest.php`. +The user would need to manually remove that cookie to be free of this effect. + +Sildisco does not otherwise deal with looking at the `'enable'` value. However, a theme for idp discovery may (e.g. simplesamlphp-module-material). + +##### SPList +In order to limit access to an IdP to only certain SP's, add an `'SPList'` array entry to the metadata for the IdP. The values of this array should match the `entity_id` values from the `sp-remote.php` metadata. + +##### excludeByDefault +If you want to require SP's to list a certain IdP in their IDPList entry in order to be able to access it, add `excludeByDefault => true` to that IdP's metadata. + +### Forced IdP discovery +The `.../lib/IdP/SAML2.php` file ensures that if an SP is allowed to access more than one IdP, then the user will be forced back to the IdP discovery page, even if they are already authenticated through one of those IdP's. + +The reason for this is to ensure that the user has a chance to decide which of their identities is used for that SP. diff --git a/features/Sp1Idp1Sp2Idp2Sp3.feature b/features/Sp1Idp1Sp2Idp2Sp3.feature new file mode 100644 index 00000000..be765055 --- /dev/null +++ b/features/Sp1Idp1Sp2Idp2Sp3.feature @@ -0,0 +1,33 @@ +Feature: Ensure I can login to Sp1 through Idp1, must login to Sp2 through Idp2 and am already logged in for Sp3. + + Scenario: Login to SP1 through IDP1 + When I go to the SP1 login page + And the url should match "sildisco/disco.php" + And I should see "to continue to SP1" + And I click on the "IDP 1" tile + And I log in using my "IDP 1" credentials + Then I should see my attributes on SP1 + + Scenario: After IDP1 login, go to SP2 through IDP2 + Given I have authenticated with IDP1 for SP1 + When I go to the SP2 login page + And I log in using my "IDP 2" credentials + Then I should see my attributes on SP2 + + Scenario: After IDP1 login, go directly to SP3 without credentials + Given I have authenticated with IDP1 for SP1 + When I go to the SP3 login page + And the url should match "sildisco/disco.php" + And I should see "to continue to SP3" + And I click on the "IDP 1" tile + Then I should see my attributes on SP3 + + Scenario: Logout of IDP1 + Given I have authenticated with IDP1 for SP1 + When I log out of IDP1 + Then I should see "You have been logged out." + + Scenario: Logout of IDP2 + Given I have authenticated with IDP2 for SP2 + When I log out of IDP2 + Then I should see "You have been logged out." diff --git a/features/Sp1Idp2Sp2Sp3Idp1.feature b/features/Sp1Idp2Sp2Sp3Idp1.feature new file mode 100644 index 00000000..aba78e0d --- /dev/null +++ b/features/Sp1Idp2Sp2Sp3Idp1.feature @@ -0,0 +1,22 @@ +Feature: Ensure I can login to Sp1 through Idp2, am already logged in for Sp2, and must login to Sp3 through Idp1. + + Scenario: Login to SP1 through IDP2 + When I go to the SP1 login page + And the url should match "sildisco/disco.php" + And I should see "to continue to SP1" + And I click on the "IDP 2" tile + And I log in using my "IDP 2" credentials + Then I should see my attributes on SP1 + + Scenario: After IDP2 login, go directly to SP2 without credentials + Given I have authenticated with IDP2 for SP1 + When I go to the SP2 login page + Then I should see my attributes on SP2 + + Scenario: After IDP2 login, go to SP3 through IDP1 + Given I have authenticated with IDP2 for SP1 + When I go to the SP3 login page + And I should see "to continue to SP3" + And I click on the "IDP 1" tile + And I log in using my "IDP 1" credentials + Then I should see my attributes on SP3 diff --git a/features/Sp2Idp2Sp1Idp1Sp3.feature b/features/Sp2Idp2Sp1Idp1Sp3.feature new file mode 100644 index 00000000..c1637991 --- /dev/null +++ b/features/Sp2Idp2Sp1Idp1Sp3.feature @@ -0,0 +1,24 @@ +Feature: Ensure I can login to Sp2 through Idp2, must login to Sp1 if I choose Idp1, and don't need to login for Sp3. + + Scenario: Login to SP2 through IDP2 + When I go to the SP2 login page + And I log in using my "IDP 2" credentials + Then I should see my attributes on SP2 + + Scenario: Login to SP1 through IDP1 + Given I have authenticated with IDP2 for SP2 + When I go to the SP1 login page + And the url should match "sildisco/disco.php" + And I click on the "IDP 1" tile + And I log in using my "IDP 1" credentials + Then I should see my attributes on SP1 + + Scenario: After IDP2 login, go directly to SP3 without credentials + Given I have authenticated with IDP2 for SP2 + And I have authenticated with IDP1 for SP1 + And I go to the SP3 login page + And the url should match "sildisco/disco.php" + And I should see "to continue to SP3" + And I click on the "IDP 1" tile + Then I should see my attributes on SP3 + diff --git a/features/Sp2Idp2Sp1Idp2Sp3.feature b/features/Sp2Idp2Sp1Idp2Sp3.feature new file mode 100644 index 00000000..e54e3244 --- /dev/null +++ b/features/Sp2Idp2Sp1Idp2Sp3.feature @@ -0,0 +1,22 @@ +Feature: Ensure I can login to Sp2 through Idp2, get discovery page for Sp1, and must login to Sp3 through Idp1. + + Scenario: Login to SP2 through IDP2 + When I go to the SP2 login page + And I log in using my "IDP 2" credentials + Then I should see my attributes on SP2 + + Scenario: Get discovery page for SP1 + Given I have authenticated with IDP2 for SP2 + When I go to the SP1 login page + And the url should match "sildisco/disco.php" + And I click on the "IDP 2" tile + Then I should see my attributes on SP1 + + Scenario: Must login to SP3 through IDP1 + Given I have authenticated with IDP2 for SP2 + When I go to the SP3 login page + And the url should match "sildisco/disco.php" + And I click on the "IDP 1" tile + And I log in using my "IDP 1" credentials + Then I should see my attributes on SP3 + diff --git a/features/Sp3Idp1Sp1Idp1Sp2Idp2.feature b/features/Sp3Idp1Sp1Idp1Sp2Idp2.feature new file mode 100644 index 00000000..c038df65 --- /dev/null +++ b/features/Sp3Idp1Sp1Idp1Sp2Idp2.feature @@ -0,0 +1,22 @@ +Feature: Ensure I can login to Sp3 through Idp1, get the discovery page for Sp1 and must login to Sp2 through Idp2. + + Scenario: login to SP3 using IDP1 + When I go to the SP3 login page + And the url should match "sildisco/disco.php" + And I should see "to continue to SP3" + And I click on the "IDP 1" tile + And I log in using my "IDP 1" credentials + Then I should see my attributes on SP3 + + Scenario: having authenticated with IDP1 for SP3, go to SP1 via the discovery page + Given I have authenticated with IDP1 for SP3 + When I go to the SP1 login page + And the url should match "sildisco/disco.php" + And I click on the "IDP 1" tile + Then I should see my attributes on SP1 + + Scenario: having authenticated with IDP1 for SP3, login to SP2 using IDP2 + Given I have authenticated with IDP1 for SP3 + When I go to the SP2 login page + And I log in using my "IDP 2" credentials + Then I should see my attributes on SP2 diff --git a/features/WwwMetadataCept.feature b/features/WwwMetadataCept.feature new file mode 100644 index 00000000..f8fb2eb4 --- /dev/null +++ b/features/WwwMetadataCept.feature @@ -0,0 +1,13 @@ +Feature: Ensure I see the hub's metadata page. + + Scenario: Show the hub's metadata page in default format + When I go to "http://ssp-hub.local/module.php/sildisco/metadata.php" + Then I should see "$metadata['ssp-hub.local']" + + Scenario: Show the hub's metadata page in XML format + When I go to "http://ssp-hub.local/module.php/sildisco/metadata.php?format=xml" + Then I should see the metadata in XML format + + Scenario: Show the hub's metadata page PHP format + When I go to "http://ssp-hub.local/module.php/sildisco/metadata.php?format=php" + Then I should see "$metadata['ssp-hub.local']" diff --git a/features/ZSp1Idp1BetaSp1Idp3.feature b/features/ZSp1Idp1BetaSp1Idp3.feature new file mode 100644 index 00000000..e82b9053 --- /dev/null +++ b/features/ZSp1Idp1BetaSp1Idp3.feature @@ -0,0 +1,13 @@ +Feature: Ensure I don't see IdP 3 at first, but after I go to the Beta Tester page I can see and login through IdP 3. + +Scenario: Normally the IdP3 is disabled + When I go to the "SP1" login page + And the url should match "sildisco/disco.php" + Then the "div" element should contain "IdP 3 coming soon" + +Scenario: After going to the "Beta Test" page, IdP3 is available for use + When I go to "http://ssp-hub.local/module.php/sildisco/betatest.php" + And I go to the "SP1" login page + And I click on the "IDP 3" tile + And I log in using my "IDP 3" credentials + Then I should see "test_admin@idp3.org" diff --git a/features/bootstrap/ExpiryContext.php b/features/bootstrap/ExpiryContext.php new file mode 100644 index 00000000..749c134b --- /dev/null +++ b/features/bootstrap/ExpiryContext.php @@ -0,0 +1,167 @@ +findAll('css', 'form'); + foreach ($forms as $form) { + if (strpos($form->getHtml(), $text) !== false) { + return; + } + } + Assert::fail(sprintf( + "No form found containing %s in this HTML:\n%s", + var_export($text, true), + $page->getHtml() + )); + } + + /** + * Assert that the given page does NOT have a form that contains the given + * text. + * + * @param string $text The text (or HTML) to search for. + * @param DocumentElement $page The page to search in. + * @return void + */ + protected function assertFormNotContains($text, $page) + { + $forms = $page->findAll('css', 'form'); + foreach ($forms as $form) { + if (strpos($form->getHtml(), $text) !== false) { + Assert::fail(sprintf( + "Found a form containing %s in this HTML:\n%s", + var_export($text, true), + $page->getHtml() + )); + } + } + } + + /** + * @Given I provide credentials that will expire in the distant future + */ + public function iProvideCredentialsThatWillExpireInTheDistantFuture() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'distant_future'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that will expire very soon + */ + public function iProvideCredentialsThatWillExpireVerySoon() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'near_future'; + $this->password = 'b'; + } + + /** + * @Then I should see a warning that my password will expire soon + */ + public function iShouldSeeAWarningThatMyPasswordWillExpireSoon() + { + $page = $this->session->getPage(); + Assert::assertContains('will expire', $page->getHtml()); + } + + /** + * @Then there should be a way to go change my password now + */ + public function thereShouldBeAWayToGoChangeMyPasswordNow() + { + $page = $this->session->getPage(); + $this->assertFormContains('change', $page); + } + + /** + * @Then there should be a way to continue without changing my password + */ + public function thereShouldBeAWayToContinueWithoutChangingMyPassword() + { + $page = $this->session->getPage(); + $this->assertFormContains('continue', $page); + } + + /** + * @Given I provide credentials that have expired + */ + public function iProvideCredentialsThatHaveExpired() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'already_past'; + $this->password = 'c'; + } + + /** + * @Then I should see a message that my password has expired + */ + public function iShouldSeeAMessageThatMyPasswordHasExpired() + { + $page = $this->session->getPage(); + Assert::assertContains('has expired', $page->getHtml()); + } + + /** + * @Then there should NOT be a way to continue without changing my password + */ + public function thereShouldNotBeAWayToContinueWithoutChangingMyPassword() + { + $page = $this->session->getPage(); + $this->assertFormNotContains('continue', $page); + } + + /** + * @Given I provide credentials that have no password expiration date + */ + public function iProvideCredentialsThatHaveNoPasswordExpirationDate() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'missing_exp'; + $this->password = 'd'; + } + + /** + * @Then I should see an error message + */ + public function iShouldSeeAnErrorMessage() + { + $page = $this->session->getPage(); + Assert::assertContains('Unhandled exception', $page->getHtml()); + } + + /** + * @Given I provide credentials that have an invalid password expiration date + */ + public function iProvideCredentialsThatHaveAnInvalidPasswordExpirationDate() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'invalid_exp'; + $this->password = 'e'; + } +} diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index bc5d23ca..748733c7 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -1,11 +1,15 @@ session->getStatusCode() . '] '; $this->printLastResponse(); @@ -89,7 +98,7 @@ public function iLogInAsAHubAdministrator() $this->logInAs('admin', 'abc123'); } - private function logInAs(string $username, string $password) + protected function logInAs(string $username, string $password) { $this->fillField('username', $username); $this->fillField('password', $password); @@ -137,69 +146,30 @@ public function iClickOnTheTile($idpName) } /** - * @When I go to the SP1 login page + * @When I go to the :sp login page */ - public function iGoToTheSp1LoginPage() + public function iGoToTheSpLoginPage($sp) { - $this->visit(self::SP1_LOGIN_PAGE); - } - - /** - * @When I log in as a user who's password is NOT about to expire - */ - public function iLogInAsAUserWhosPasswordIsNotAboutToExpire() - { - $this->logInAs('distant_future', 'a'); + switch ($sp) { + case 'SP1': + $this->visit(self::SP1_LOGIN_PAGE); + break; + case 'SP2': + $this->visit(self::SP2_LOGIN_PAGE); + break; + case 'SP3': + $this->visit(self::SP3_LOGIN_PAGE); + break; + } } - /** - * @Then I should see a page indicating that I successfully logged in - */ - public function iShouldSeeAPageIndicatingThatISuccessfullyLoggedIn() - { - $this->assertResponseStatus(200); - $this->assertPageBodyContainsText('Your attributes'); - } - - private function assertPageBodyContainsText(string $expectedText) + protected function assertPageBodyContainsText(string $expectedText) { $page = $this->session->getPage(); $body = $page->find('css', 'body'); Assert::contains($body->getText(), $expectedText); } - /** - * @When I log in as a user who's password is about to expire - */ - public function iLogInAsAUserWhosPasswordIsAboutToExpire() - { - $this->logInAs('near_future', 'a'); - } - - /** - * @Then I should see a page warning me that my password is about to expire - */ - public function iShouldSeeAPageWarningMeThatMyPasswordIsAboutToExpire() - { - $this->assertPageBodyContainsText('Password expiring soon'); - } - - /** - * @When I log in as a user who's password has expired - */ - public function iLogInAsAUserWhosPasswordHasExpired() - { - $this->logInAs('already_past', 'a'); - } - - /** - * @Then I should see a page telling me that my password has expired - */ - public function iShouldSeeAPageTellingMeThatMyPasswordHasExpired() - { - $this->assertPageBodyContainsText('Your password has expired'); - } - private static function ensureFolderExistsForTestFile($filePath) { $folder = dirname($filePath); @@ -247,4 +217,118 @@ public function theFileShouldContain($filePath, PyStringNode $expectedJson) json_decode($expectedJson, true) ); } + + /** + * Get the login button from the given page. + * + * @param DocumentElement $page The page. + * @return NodeElement + */ + protected function getLoginButton($page) + { + $buttons = $page->findAll('css', 'button'); + $loginButton = null; + foreach ($buttons as $button) { + $lcButtonText = strtolower($button->getText()); + if (strpos($lcButtonText, 'login') !== false) { + $loginButton = $button; + break; + } + } + Assert::notNull($loginButton, 'Failed to find the login button'); + return $loginButton; + } + + /** + * @When I log in + */ + public function iLogIn() + { + $page = $this->session->getPage(); + try { + $page->fillField('username', $this->username); + $page->fillField('password', $this->password); + $this->submitLoginForm($page); + } catch (ElementNotFoundException $e) { + Assert::true(false, sprintf( + "Did not find that element in the page.\nError: %s\nPage content: %s", + $e->getMessage(), + $page->getContent() + )); + } + } + + /** + * @Given I have logged in (again) + */ + public function iHaveLoggedIn() + { + $this->iLogin(); + } + + /** + * Submit the current form, including the secondary page's form (if + * simpleSAMLphp shows another page because JavaScript isn't supported) by + * clicking the specified button. + * + * @param string $buttonName The value of the desired button's `name` + * attribute. + */ + protected function submitFormByClickingButtonNamed($buttonName) + { + $page = $this->session->getPage(); + $button = $page->find('css', sprintf( + '[name=%s]', + $buttonName + )); + Assert::notNull($button, 'Failed to find button named ' . $buttonName); + $button->click(); + $this->submitSecondarySspFormIfPresent($page); + } + + /** + * Submit the login form, including the secondary page's form (if + * simpleSAMLphp shows another page because JavaScript isn't supported). + * + * @param DocumentElement $page The page. + */ + protected function submitLoginForm($page) + { + $loginButton = $this->getLoginButton($page); + $loginButton->click(); + $this->submitSecondarySspFormIfPresent($page); + } + + /** + * Submit the secondary page's form (if simpleSAMLphp shows another page + * because JavaScript isn't supported). + * + * @param DocumentElement $page The page. + */ + protected function submitSecondarySspFormIfPresent($page) + { + // SimpleSAMLphp 1.15 markup for secondary page: + $postLoginSubmitButton = $page->findButton('postLoginSubmitButton'); + if ($postLoginSubmitButton instanceof NodeElement) { + $postLoginSubmitButton->click(); + } else { + + // SimpleSAMLphp 1.14 markup for secondary page: + $body = $page->find('css', 'body'); + if ($body instanceof NodeElement) { + $onload = $body->getAttribute('onload'); + if ($onload === "document.getElementsByTagName('input')[0].click();") { + $body->pressButton('Submit'); + } + } + } + } + + /** + * @Then I should end up at my intended destination + */ + public function iShouldEndUpAtMyIntendedDestination() + { + $this->assertPageBodyContainsText('Your attributes'); + } } diff --git a/features/bootstrap/LoginContext.php b/features/bootstrap/LoginContext.php new file mode 100644 index 00000000..7425e2b7 --- /dev/null +++ b/features/bootstrap/LoginContext.php @@ -0,0 +1,635 @@ + ['db' => [ + 'dsn' => sprintf( + 'mysql:host=%s;dbname=%s', + Env::get('MYSQL_HOST'), + Env::get('MYSQL_DATABASE') + ), + 'username' => Env::get('MYSQL_USER'), + 'password' => Env::get('MYSQL_PASSWORD'), + ]]]); + + $this->logger = new Psr3EchoLogger(); + + $this->captcha = new Captcha(); + $this->idBroker = new IdBroker( + 'http://fake.example.com/api/', + 'FakeAccessToken', + $this->logger, + 'fake.example.com', + [], + false + ); + $this->request = new Request(); + + $this->resetDatabase(); + } + + protected function addXFailedLoginUsernames(int $number, $username) + { + Assert::notEmpty($username); + + for ($i = 0; $i < $number; $i++) { + $newRecord = new FailedLoginUsername(['username' => $username]); + Assert::true($newRecord->save()); + } + + Assert::count( + FailedLoginUsername::getFailedLoginsFor($username), + $number + ); + } + + protected function login() + { + $this->authenticator = new Authenticator( + $this->username, + $this->password, + $this->request, + $this->captcha, + $this->idBroker, + $this->logger + ); + } + + protected function loginXTimes($numberOfTimes) + { + for ($i = 0; $i < $numberOfTimes; $i++) { + $this->login(); + } + } + + protected function resetDatabase() + { + FailedLoginIpAddress::deleteAll(); + FailedLoginUsername::deleteAll(); + } + + /** + * @Given I provide a username + */ + public function iProvideAUsername() + { + $this->username = 'a username'; + } + + /** + * @Given I provide a password + */ + public function iProvideAPassword() + { + $this->password = 'a password'; + } + + /** + * @When I try to log in + */ + public function iTryToLogIn() + { + $this->login(); + } + + /** + * @Then I should not be allowed through + */ + public function iShouldNotBeAllowedThrough() + { + Assert::false( + $this->authenticator->isAuthenticated() + ); + $authenticator = $this->authenticator; + Assert::throws( + function() use ($authenticator) { + $authenticator->getUserAttributes(); + }, + \Exception::class, + 'The call to getUserAttributes() should have thrown an exception.' + ); + } + + /** + * @Given I do not provide a username + */ + public function iDoNotProvideAUsername() + { + $this->username = ''; + } + + /** + * @Then I should see an error message with :text in it + */ + public function iShouldSeeAnErrorMessageWithInIt($text) + { + $authError = $this->authenticator->getAuthError(); + Assert::notEmpty($authError); + Assert::contains((string)$authError, $text); + } + + /** + * @Given I do not provide a password + */ + public function iDoNotProvideAPassword() + { + $this->password = ''; + } + + /** + * @Given I fail the captcha + */ + public function iFailTheCaptcha() + { + $this->captcha = new DummyFailedCaptcha(); + } + + /** + * @Then I should see a generic invalid-login error message + */ + public function iShouldSeeAGenericInvalidLoginErrorMessage() + { + $authError = $this->authenticator->getAuthError(); + Assert::notEmpty($authError); + Assert::contains((string)$authError, 'invalid_login'); + } + + /** + * @Given I provide a username of :username + */ + public function iProvideAUsernameOf($username) + { + $this->username = $username; + } + + /** + * @Then I should see an error message telling me to wait + */ + public function iShouldSeeAnErrorMessageTellingMeToWait() + { + $authError = $this->authenticator->getAuthError(); + Assert::notEmpty($authError); + Assert::contains((string)$authError, 'rate_limit'); + } + + /** + * @Given I provide an incorrect password + */ + public function iProvideAnIncorrectPassword() + { + $this->password = 'dummy incorrect password'; + $this->idBroker = new FakeFailedIdBroker('fake', 'fake', $this->logger); + } + + /** + * @Given that username will be rate limited after one more failed attempt + */ + public function thatUsernameWillBeRateLimitedAfterOneMoreFailedAttempt() + { + FailedLoginUsername::resetFailedLoginsBy($this->username); + + $this->addXFailedLoginUsernames( + Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN - 1, + $this->username + ); + } + + /** + * @Given I (then) provide the correct password for that username + */ + public function iProvideTheCorrectPasswordForThatUsername() + { + Assert::notEmpty($this->username); + $this->password = 'dummy correct password'; + $this->idBroker = new FakeSuccessfulIdBroker('fake', 'fake', $this->logger); + } + + /** + * @Then I should not see an error message + */ + public function iShouldNotSeeAnErrorMessage() + { + $authError = $this->authenticator->getAuthError(); + Assert::isEmpty( + $authError, + "Unexpected error: \n- " . $authError + ); + } + + /** + * @Then I should be allowed through + */ + public function iShouldBeAllowedThrough() + { + Assert::true( + $this->authenticator->isAuthenticated() + ); + $userInfo = $this->authenticator->getUserAttributes(); + Assert::notEmpty($userInfo); + } + + /** + * @When I try to log in enough times to trigger the rate limit + */ + public function iTryToLogInEnoughTimesToTriggerTheRateLimit() + { + $this->loginXTimes( + Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN + ); + } + + /** + * @Given that username has :number more recent failed logins than the limit + */ + public function thatUsernameHasMoreRecentFailedLoginsThanTheLimit($number) + { + Assert::true(is_numeric($number)); + + FailedLoginUsername::resetFailedLoginsBy($this->username); + + $this->addXFailedLoginUsernames( + $number + Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN, + $this->username + ); + } + + /** + * @Then I should see an error message with :text1 and :text2 in it + */ + public function iShouldSeeAnErrorMessageWithAndInIt($text1, $text2) + { + $authError = $this->authenticator->getAuthError(); + Assert::notEmpty($authError); + $authErrorString = (string)$authError; + Assert::contains($authErrorString, $text1); + Assert::contains($authErrorString, $text2); + } + + /** + * @Given that username has enough failed logins to require a captcha + */ + public function thatUsernameHasEnoughFailedLoginsToRequireACaptcha() + { + FailedLoginUsername::resetFailedLoginsBy($this->username); + + $this->addXFailedLoginUsernames( + Authenticator::REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN, + $this->username + ); + } + + /** + * @Given that username has no recent failed login attempts + */ + public function thatUsernameHasNoRecentFailedLoginAttempts() + { + Assert::notEmpty($this->username); + FailedLoginUsername::resetFailedLoginsBy($this->username); + Assert::eq( + 0, + FailedLoginUsername::countRecentFailedLoginsFor($this->username) + ); + } + + /** + * @Then that username should be blocked for awhile + */ + public function thatUsernameShouldBeBlockedForAwhile() + { + Assert::notEmpty($this->username); + Assert::true( + FailedLoginUsername::isRateLimitBlocking($this->username) + ); + } + + /** + * @Given my request comes from IP address :ipAddress + */ + public function myRequestComesFromIpAddress($ipAddress) + { + if ( ! $this->request instanceof DummyRequest) { + $this->request = new DummyRequest(); + } + + $this->request->setDummyIpAddress($ipAddress); + } + + /** + * @Then that IP address should be blocked for awhile + */ + public function thatIpAddressShouldBeBlockedForAwhile() + { + $ipAddresses = $this->request->getUntrustedIpAddresses(); + Assert::count($ipAddresses, 1); + $ipAddress = $ipAddresses[0]; + + Assert::true( + FailedLoginIpAddress::isRateLimitBlocking($ipAddress) + ); + } + + /** + * @Then that username's failed login attempts should be at :number + */ + public function thatUsernameSFailedLoginAttemptsShouldBeAt($number) + { + Assert::notEmpty($this->username); + Assert::true(is_numeric($number)); + Assert::count( + FailedLoginUsername::getFailedLoginsFor($this->username), + (int)$number + ); + } + + /** + * @Given that username does not have enough failed logins to require a captcha + */ + public function thatUsernameDoesNotHaveEnoughFailedLoginsToRequireACaptcha() + { + Assert::notEmpty($this->username); + FailedLoginUsername::deleteAll(); + Assert::isEmpty(FailedLoginUsername::getFailedLoginsFor($this->username)); + } + + /** + * @Given my IP address has enough failed logins to require a captcha + */ + public function myIpAddressHasEnoughFailedLoginsToRequireACaptcha() + { + $ipAddress = $this->request->getMostLikelyIpAddress(); + Assert::notNull($ipAddress, 'No IP address was provided.'); + FailedLoginIpAddress::deleteAll(); + Assert::isEmpty(FailedLoginIpAddress::getFailedLoginsFor($ipAddress)); + + $desiredCount = Authenticator::REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN; + + for ($i = 0; $i < $desiredCount; $i++) { + $failedLoginIpAddress = new FailedLoginIpAddress([ + 'ip_address' => $ipAddress, + ]); + Assert::true($failedLoginIpAddress->save()); + } + + Assert::eq( + Authenticator::REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN, + FailedLoginIpAddress::countRecentFailedLoginsFor($ipAddress) + ); + } + + /** + * @Given that username has enough failed logins to be blocked by the rate limit + */ + public function thatUsernameHasEnoughFailedLoginsToBeBlockedByTheRateLimit() + { + FailedLoginUsername::resetFailedLoginsBy($this->username); + + $this->addXFailedLoginUsernames( + Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN, + $this->username + ); + } + + /** + * @Given that IP address has triggered the rate limit + */ + public function thatIpAddressHasTriggeredTheRateLimit() + { + $ipAddresses = $this->request->getUntrustedIpAddresses(); + Assert::count($ipAddresses, 1); + $ipAddress = $ipAddresses[0]; + + FailedLoginIpAddress::deleteAll(); + Assert::isEmpty(FailedLoginIpAddress::getFailedLoginsFor($ipAddress)); + + $desiredCount = Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN; + + for ($i = 0; $i < $desiredCount; $i++) { + $failedLoginIpAddress = new FailedLoginIpAddress([ + 'ip_address' => $ipAddress, + ]); + Assert::true($failedLoginIpAddress->save()); + } + + Assert::true( + FailedLoginIpAddress::isRateLimitBlocking($ipAddress) + ); + } + + /** + * @Given /^I pass (the|any) captchas?$/ + */ + public function iPassTheCaptcha() + { + $this->captcha = new DummySuccessfulCaptcha(); + } + + /** + * @Given that username has :number more non-recent failed logins than the limit + */ + public function thatUsernameHasMoreNonRecentFailedLoginsThanTheLimit($number) + { + Assert::notEmpty($this->username); + Assert::true(is_numeric($number)); + + $desiredNumber = $number + Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN; + + $numTotalFailures = count(FailedLoginUsername::getFailedLoginsFor($this->username)); + $numRecentFailures = FailedLoginUsername::countRecentFailedLoginsFor($this->username); + $numNonRecentFailures = $numTotalFailures - $numRecentFailures; + + for ($i = $numNonRecentFailures; $i < $desiredNumber; $i++) { + $failedLoginUsername = new FailedLoginUsername([ + 'username' => $this->username, + + // NOTE: Use some time (UTC) longer ago than we consider "recent". + 'occurred_at_utc' => new UtcTime('-1 month'), + ]); + // NOTE: Don't validate, as that would overwrite the datetime field. + Assert::true($failedLoginUsername->save(false)); + } + + $numTotalFailuresPost = count(FailedLoginUsername::getFailedLoginsFor($this->username)); + $numRecentFailuresPost = FailedLoginUsername::countRecentFailedLoginsFor($this->username); + $numNonRecentFailuresPost = $numTotalFailuresPost - $numRecentFailuresPost; + + Assert::eq($desiredNumber, $numNonRecentFailuresPost); + } + + /** + * @Then I should not have to pass a captcha test for that user + */ + public function iShouldNotHaveToPassACaptchaTestForThatUser() + { + Assert::notEmpty($this->username); + Assert::false( + FailedLoginUsername::isCaptchaRequiredFor($this->username) + ); + } + + /** + * @Given :ipAddress is a trusted IP address + */ + public function isATrustedIpAddress($ipAddress) + { + $this->request->trustIpAddress($ipAddress); + } + + /** + * @Then the IP address :ipAddress should not have any failed login attempts + */ + public function theIpAddressShouldNotHaveAnyFailedLoginAttempts($ipAddress) + { + Assert::true(Request::isValidIpAddress($ipAddress)); + Assert::isEmpty(FailedLoginIpAddress::getFailedLoginsFor($ipAddress)); + } + + /** + * @Given the ID Broker is returning invalid responses + */ + public function theIdBrokerIsReturningInvalidResponses() + { + $this->idBroker = new FakeInvalidIdBroker('fake', 'fake', $this->logger); + } + + /** + * @Then I should see a generic try-later error message + */ + public function iShouldSeeAGenericTryLaterErrorMessage() + { + $authError = $this->authenticator->getAuthError(); + Assert::notEmpty($authError); + Assert::contains((string)$authError, 'later'); + } + + /** + * @Given :ipAddressRange is a trusted IP address range + */ + public function isATrustedIpAddressRange($ipAddressRange) + { + $this->request->trustIpAddressRange($ipAddressRange); + } + + /** + * @Then the IP address :ipAddress should have a failed login attempt + */ + public function theIpAddressShouldHaveAFailedLoginAttempt($ipAddress) + { + Assert::notEmpty($ipAddress); + Assert::count( + FailedLoginIpAddress::getFailedLoginsFor($ipAddress), + 1 + ); + } + + /** + * @Given :numSeconds seconds ago that username had :numFailuresBeyondLimit more failed logins than the limit + */ + public function secondsAgoThatUsernameHadMoreFailedLoginsThanTheLimit( + $numSeconds, + $numFailuresBeyondLimit + ) { + Assert::notEmpty($this->username); + Assert::true(is_numeric($numSeconds)); + Assert::true(is_numeric($numFailuresBeyondLimit)); + + FailedLoginUsername::resetFailedLoginsBy($this->username); + + $numDesiredFailuresTotal = $numFailuresBeyondLimit + Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN; + + for ($i = 0; $i < $numDesiredFailuresTotal; $i++) { + $failedLoginUsername = new FailedLoginUsername([ + 'username' => $this->username, + 'occurred_at_utc' => new UtcTime(sprintf( + '-%s seconds', + $numSeconds + )), + ]); + // NOTE: Don't validate, as that would overwrite the datetime field. + Assert::true($failedLoginUsername->save(false)); + } + + $numTotalFailuresPost = count(FailedLoginUsername::getFailedLoginsFor($this->username)); + + Assert::eq($numDesiredFailuresTotal, $numTotalFailuresPost); + } + + /** + * @Given :numSeconds seconds ago the IP address :ipAddress had :numFailuresBeyondLimit more failed logins than the limit + */ + public function secondsAgoTheIpAddressHadMoreFailedLoginsThanTheLimit( + $numSeconds, + $ipAddress, + $numFailuresBeyondLimit + ) { + Assert::notEmpty($ipAddress); + Assert::true(is_numeric($numSeconds)); + Assert::true(is_numeric($numFailuresBeyondLimit)); + + FailedLoginIpAddress::resetFailedLoginsBy([$ipAddress]); + + $numDesiredFailuresTotal = $numFailuresBeyondLimit + Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN; + + for ($i = 0; $i < $numDesiredFailuresTotal; $i++) { + $failedLoginIpAddress = new FailedLoginIpAddress([ + 'ip_address' => $ipAddress, + 'occurred_at_utc' => new UtcTime(sprintf( + '-%s seconds', + $numSeconds + )), + ]); + // NOTE: Don't validate, as that would overwrite the datetime field. + Assert::true($failedLoginIpAddress->save(false)); + } + + $numTotalFailuresPost = count(FailedLoginIpAddress::getFailedLoginsFor($ipAddress)); + + Assert::eq($numDesiredFailuresTotal, $numTotalFailuresPost); + } +} diff --git a/features/bootstrap/MfaContext.php b/features/bootstrap/MfaContext.php new file mode 100644 index 00000000..16dbdaa0 --- /dev/null +++ b/features/bootstrap/MfaContext.php @@ -0,0 +1,704 @@ +findAll('css', 'form'); + foreach ($forms as $form) { + if (strpos($form->getHtml(), $text) !== false) { + return; + } + } + Assert::fail(sprintf( + "No form found containing %s in this HTML:\n%s", + var_export($text, true), + $page->getHtml() + )); + } + + /** + * Get the "continue" button. + * + * @param DocumentElement $page The page. + * @return NodeElement + */ + protected function getContinueButton($page) + { + $continueButton = $page->find('css', '[name=continue]'); + return $continueButton; + } + + /** + * Get the button for submitting the MFA form. + * + * @param DocumentElement $page The page. + * @return NodeElement + */ + protected function getSubmitMfaButton($page) + { + $submitMfaButton = $page->find('css', '[name=submitMfa]'); + Assert::assertNotNull($submitMfaButton, 'Failed to find the submit-MFA button'); + return $submitMfaButton; + } + + /** + * Submit the current form, including the secondary page's form (if + * simpleSAMLphp shows another page because JavaScript isn't supported) by + * clicking the specified button. + * + * @param string $buttonName The value of the desired button's `name` + * attribute. + */ + protected function submitFormByClickingButtonNamed($buttonName) + { + $page = $this->session->getPage(); + $button = $page->find('css', sprintf( + '[name=%s]', + $buttonName + )); + Assert::assertNotNull($button, 'Failed to find button named ' . $buttonName); + $button->click(); + $this->submitSecondarySspFormIfPresent($page); + } + + /** + * Submit the MFA form, including the secondary page's form (if + * simpleSAMLphp shows another page because JavaScript isn't supported). + * + * @param DocumentElement $page The page. + */ + protected function submitMfaForm($page) + { + $submitMfaButton = $this->getSubmitMfaButton($page); + $submitMfaButton->click(); + $this->submitSecondarySspFormIfPresent($page); + } + + /** + * @Given I provide credentials that do not need MFA + */ + public function iProvideCredentialsThatDoNotNeedMfa() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'no_mfa_needed'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that need MFA but have no MFA options available + */ + public function iProvideCredentialsThatNeedMfaButHaveNoMfaOptionsAvailable() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'must_set_up_mfa'; + $this->password = 'a'; + } + + /** + * @Then I should see a message that I have to set up MFA + */ + public function iShouldSeeAMessageThatIHaveToSetUpMfa() + { + $page = $this->session->getPage(); + Assert::assertContains('must set up 2-', $page->getHtml()); + } + + /** + * @Then there should be a way to go set up MFA now + */ + public function thereShouldBeAWayToGoSetUpMfaNow() + { + $page = $this->session->getPage(); + $this->assertFormContains('name="setUpMfa"', $page); + } + + /** + * @Given I provide credentials that need MFA and have backup codes available + */ + public function iProvideCredentialsThatNeedMfaAndHaveBackupCodesAvailable() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_backupcode'; + $this->password = 'a'; + } + + /** + * @Then I should see a prompt for a backup code + */ + public function iShouldSeeAPromptForABackupCode() + { + $page = $this->session->getPage(); + $pageHtml = $page->getHtml(); + Assert::assertContains('

Printable Backup Code

', $pageHtml); + Assert::assertContains('Enter code', $pageHtml); + } + + /** + * @Given I provide credentials that need MFA and have TOTP available + */ + public function iProvideCredentialsThatNeedMfaAndHaveTotpAvailable() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_totp'; + $this->password = 'a'; + } + + /** + * @Then I should see a prompt for a TOTP (code) + */ + public function iShouldSeeAPromptForATotpCode() + { + $page = $this->session->getPage(); + $pageHtml = $page->getHtml(); + Assert::assertContains('

Smartphone App

', $pageHtml); + Assert::assertContains('Enter 6-digit code', $pageHtml); + } + + /** + * @Given I provide credentials that need MFA and have WebAuthn available + */ + public function iProvideCredentialsThatNeedMfaAndHaveUfAvailable() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_webauthn'; + $this->password = 'a'; + } + + /** + * @Then I should see a prompt for a WebAuthn (security key) + */ + public function iShouldSeeAPromptForAWebAuthn() + { + $page = $this->session->getPage(); + Assert::assertContains('

USB Security Key

', $page->getHtml()); + } + + protected function submitMfaValue($mfaValue) + { + $page = $this->session->getPage(); + $page->fillField('mfaSubmission', $mfaValue); + $this->submitMfaForm($page); + return $page->getHtml(); + } + + /** + * @When I submit a correct backup code + */ + public function iSubmitACorrectBackupCode() + { + if (! $this->pageContainsElementWithText('h2', 'Printable Backup Code')) { + $this->clickLink('backupcode'); + } + $this->submitMfaValue(FakeIdBrokerClient::CORRECT_VALUE); + } + + protected function pageContainsElementWithText($cssSelector, $text) + { + $page = $this->session->getPage(); + $elements = $page->findAll('css', $cssSelector); + foreach ($elements as $element) { + if (strpos($element->getText(), $text) !== false) { + return true; + } + } + return false; + } + + /** + * @When I submit an incorrect backup code + */ + public function iSubmitAnIncorrectBackupCode() + { + $this->submitMfaValue(FakeIdBrokerClient::INCORRECT_VALUE); + } + + /** + * @Then I should see a message that I have to wait before trying again + */ + public function iShouldSeeAMessageThatIHaveToWaitBeforeTryingAgain() + { + $page = $this->session->getPage(); + $pageHtml = $page->getHtml(); + Assert::assertContains(' wait ', $pageHtml); + Assert::assertContains('try again', $pageHtml); + } + + /** + * @Then I should see a message that it was incorrect + */ + public function iShouldSeeAMessageThatItWasIncorrect() + { + $page = $this->session->getPage(); + $pageHtml = $page->getHtml(); + Assert::assertContains('Incorrect 2-step verification code', $pageHtml); + } + + /** + * @Given I provide credentials that have a rate-limited MFA + */ + public function iProvideCredentialsThatHaveARateLimitedMfa() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_rate_limited_mfa'; + $this->password = 'a'; + } + + /** + * @Then there should be a way to continue to my intended destination + */ + public function thereShouldBeAWayToContinueToMyIntendedDestination() + { + $page = $this->session->getPage(); + $this->assertFormContains('name="continue"', $page); + } + + /** + * @When I click the remind-me-later button + */ + public function iClickTheRemindMeLaterButton() + { + $this->submitFormByClickingButtonNamed('continue'); + } + + /** + * @When I click the set-up-MFA button + */ + public function iClickTheSetUpMfaButton() + { + $this->submitFormByClickingButtonNamed('setUpMfa'); + } + + /** + * @Then I should end up at the mfa-setup URL + */ + public function iShouldEndUpAtTheMfaSetupUrl() + { + $mfaSetupUrl = Env::get('PROFILE_URL_FOR_TESTS'); + Assert::assertNotEmpty($mfaSetupUrl, 'No PROFILE_URL_FOR_TESTS provided'); + $currentUrl = $this->session->getCurrentUrl(); + Assert::assertStringStartsWith( + $mfaSetupUrl, + $currentUrl, + 'Did NOT end up at the MFA-setup URL' + ); + } + + /** + * @Then there should NOT be a way to continue to my intended destination + */ + public function thereShouldNotBeAWayToContinueToMyIntendedDestination() + { + $page = $this->session->getPage(); + $continueButton = $this->getContinueButton($page); + Assert::assertNull($continueButton, 'Should not have found a continue button'); + } + + /** + * @Then I should NOT be able to get to my intended destination + */ + public function iShouldNotBeAbleToGetToMyIntendedDestination() + { + $this->session->visit(self::SP1_LOGIN_PAGE); + Assert::assertStringStartsNotWith( + self::SP1_LOGIN_PAGE, + $this->session->getCurrentUrl(), + 'Failed to prevent me from getting to SPs other than the MFA setup URL' + ); + } + + /** + * @Given I provide credentials that need MFA and have 4 backup codes available + */ + public function iProvideCredentialsThatNeedMfaAndHave4BackupCodesAvailable() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_4_backupcodes'; + $this->password = 'a'; + } + + /** + * @Then I should see a message that I am running low on backup codes + */ + public function iShouldSeeAMessageThatIAmRunningLowOnBackupCodes() + { + $page = $this->session->getPage(); + Assert::assertContains( + 'You are almost out of Printable Backup Codes', + $page->getHtml() + ); + } + + /** + * @Then there should be a way to get more backup codes now + */ + public function thereShouldBeAWayToGetMoreBackupCodesNow() + { + $page = $this->session->getPage(); + $this->assertFormContains('name="getMore"', $page); + } + + /** + * @Given I provide credentials that need MFA and have 1 backup code available and no other MFA + */ + public function iProvideCredentialsThatNeedMfaAndHave1BackupCodeAvailableAndNoOtherMfa() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_1_backupcode_only'; + $this->password = 'a'; + } + + /** + * @Then I should see a message that I have used up my backup codes + */ + public function iShouldSeeAMessageThatIHaveUsedUpMyBackupCodes() + { + $page = $this->session->getPage(); + Assert::assertContains( + 'You just used your last Printable Backup Code', + $page->getHtml() + ); + } + + /** + * @Given I provide credentials that need MFA and have 1 backup code available plus some other MFA + */ + public function iProvideCredentialsThatNeedMfaAndHave1BackupCodeAvailablePlusSomeOtherMfa() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_1_backupcode_plus'; + $this->password = 'a'; + } + + /** + * @When I click the get-more-backup-codes button + */ + public function iClickTheGetMoreBackupCodesButton() + { + $this->submitFormByClickingButtonNamed('getMore'); + } + + /** + * @Then I should be told I only have :numRemaining backup codes left + */ + public function iShouldBeToldIOnlyHaveBackupCodesLeft($numRemaining) + { + $page = $this->session->getPage(); + Assert::assertContains( + 'You only have ' . $numRemaining . ' remaining', + $page->getHtml() + ); + } + + /** + * @Then I should be given more backup codes + */ + public function iShouldBeGivenMoreBackupCodes() + { + $page = $this->session->getPage(); + Assert::assertContains( + 'Here are your new Printable Backup Codes', + $page->getContent() + ); + } + + /** + * @Given I provide credentials that have WebAuthn + */ + public function iProvideCredentialsThatHaveUf() + { + $this->iProvideCredentialsThatNeedMfaAndHaveUfAvailable(); + } + + /** + * @Given the user's browser supports WebAuthn + */ + public function theUsersBrowserSupportsUf() + { + $userAgentWithWebAuthn = self::USER_AGENT_WITH_WEBAUTHN_SUPPORT; + Assert::assertTrue( + LoginBrowser::supportsWebAuthn($userAgentWithWebAuthn), + 'Update USER_AGENT_WITH_WEBAUTHN_SUPPORT to a User Agent with WebAuthn support' + ); + +// $this->driver->getClient()->setServerParameter('HTTP_USER_AGENT', $userAgentWithWebAuthn); + } + + /** + * @Given I provide credentials that have WebAuthn, TOTP + */ + public function iProvideCredentialsThatHaveUfTotp() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_webauthn_totp'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that have WebAuthn, backup codes + */ + public function iProvideCredentialsThatHaveUfBackupCodes() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_webauthn_backupcodes'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that have WebAuthn, TOTP, backup codes + */ + public function iProvideCredentialsThatHaveUfTotpBackupCodes() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_webauthn_totp_backupcodes'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that have TOTP + */ + public function iProvideCredentialsThatHaveTotp() + { + $this->iProvideCredentialsThatNeedMfaAndHaveTotpAvailable(); + } + + /** + * @Given I provide credentials that have TOTP, backup codes + */ + public function iProvideCredentialsThatHaveTotpBackupCodes() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_totp_backupcodes'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that have backup codes + */ + public function iProvideCredentialsThatHaveBackupCodes() + { + $this->iProvideCredentialsThatNeedMfaAndHaveBackupCodesAvailable(); + } + + /** + * @Given I provide credentials that have a manager code, a WebAuthn and a more recently used TOTP + */ + public function IProvideCredentialsThatHaveManagerCodeWebauthnAndMoreRecentlyUsedTotp() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_mgr_code_webauthn_and_more_recently_used_totp'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that have a used WebAuthn + */ + public function IProvideCredentialsThatHaveUsedWebAuthn() + { + $this->username = 'has_webauthn_'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that have a used TOTP + */ + public function IProvideCredentialsThatHaveUsedTotp() + { + $this->username = 'has_totp_'; + $this->password = 'a'; + } + + /** + * @Given I provide credentials that have a used backup code + */ + public function IProvideCredentialsThatHaveUsedBackupCode() + { + $this->username = 'has_backup_code_'; + $this->password = 'a'; + } + + /** + * @Given and I have a more recently used TOTP + */ + public function IHaveMoreRecentlyUsedTotp() + { + $this->username .= 'and_more_recently_used_totp'; + $this->password = 'a'; + } + + /** + * @Given and I have a more recently used Webauthn + */ + public function IHaveMoreRecentlyUsedWebauthn() + { + $this->username .= 'and_more_recently_used_webauthn'; + $this->password = 'a'; + } + + /** + * @Given and I have a more recently used backup code + */ + public function IHaveMoreRecentlyUsedBackupCode() + { + $this->username .= 'and_more_recently_used_backup_code'; + $this->password = 'a'; + } + + /** + * @Given the user's browser does not support WebAuthn + */ + public function theUsersBrowserDoesNotSupportUf() + { + $userAgentWithoutWebAuthn = self::USER_AGENT_WITHOUT_WEBAUTHN_SUPPORT; + Assert::assertFalse( + LoginBrowser::supportsWebAuthn($userAgentWithoutWebAuthn), + 'Update USER_AGENT_WITHOUT_WEBAUTHN_SUPPORT to a User Agent without WebAuthn support' + ); + +// $this->driver->getClient()->setServerParameter('HTTP_USER_AGENT', $userAgentWithoutWebAuthn); + } + + /** + * @Then I should not see an error message about WebAuthn being unsupported + */ + public function iShouldNotSeeAnErrorMessageAboutUfBeingUnsupported() + { + $page = $this->session->getPage(); + Assert::assertNotContains('USB Security Keys are not supported', $page->getContent()); + } + + /** + * @Then I should see an error message about WebAuthn being unsupported + */ + public function iShouldSeeAnErrorMessageAboutUfBeingUnsupported() + { + $page = $this->session->getPage(); + Assert::assertContains('USB Security Keys are not supported', $page->getContent()); + } + + /** + * @Given the user has a manager email + */ + public function theUserHasAManagerEmail() + { + $this->username .= '_and_mgr'; + } + + /** + * @Then I should see a link to send a code to the user's manager + */ + public function iShouldSeeALinkToSendACodeToTheUsersManager() + { + $page = $this->session->getPage(); + Assert::assertContains('Can\'t use any of your 2-Step Verification options', $page->getContent()); + } + + /** + * @Given the user does not have a manager email + */ + public function theUserDoesntHaveAManagerEmail() + { + /* + * No change to username needed. + */ + } + + /** + * @Then I should not see a link to send a code to the user's manager + */ + public function iShouldNotSeeALinkToSendACodeToTheUsersManager() + { + $page = $this->session->getPage(); + Assert::assertNotContains('Send a code to your manager', $page->getContent()); + } + + /** + * @When I click the Request Assistance link + */ + public function iClickTheRequestAssistanceLink() + { + $this->clickLink('Click here'); + } + + /** + * @When I click the Send a code link + */ + public function iClickTheRequestACodeLink() + { + $this->submitFormByClickingButtonNamed('send'); + } + + /** + * @Then I should see a prompt for a manager rescue code + */ + public function iShouldSeeAPromptForAManagerRescueCode() + { + $page = $this->session->getPage(); + $pageHtml = $page->getHtml(); + Assert::assertContains('

Manager Rescue Code

', $pageHtml); + Assert::assertContains('Enter code', $pageHtml); + } + + /** + * @When I submit the correct manager code + */ + public function iSubmitTheCorrectManagerCode() + { + $this->submitMfaValue(FakeIdBrokerClient::CORRECT_VALUE); + } + + /** + * @When I submit an incorrect manager code + */ + public function iSubmitAnIncorrectManagerCode() + { + $this->submitMfaValue(FakeIdBrokerClient::INCORRECT_VALUE); + } + + /** + * @Given I provide credentials that have a manager code + */ + public function iProvideCredentialsThatHaveAManagerCode() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'has_mgr_code'; + $this->password = 'a'; + } + + /** + * @Then there should be a way to request a manager code + */ + public function thereShouldBeAWayToRequestAManagerCode() + { + $page = $this->session->getPage(); + $this->assertFormContains('name="send"', $page); + } + + /** + * @When I click the Cancel button + */ + public function iClickTheCancelButton() + { + $this->submitFormByClickingButtonNamed('cancel'); + } +} diff --git a/features/bootstrap/ProfileReviewContext.php b/features/bootstrap/ProfileReviewContext.php new file mode 100644 index 00000000..9c88f526 --- /dev/null +++ b/features/bootstrap/ProfileReviewContext.php @@ -0,0 +1,177 @@ +findAll('css', 'form'); + foreach ($forms as $form) { + if (strpos($form->getHtml(), $text) !== false) { + return; + } + } + Assert::fail(sprintf( + "No form found containing %s in this HTML:\n%s", + var_export($text, true), + $page->getHtml() + )); + } + + /** + * Submit the current form, including the secondary page's form (if + * simpleSAMLphp shows another page because JavaScript isn't supported) by + * clicking the specified button. + * + * @param string $buttonName The value of the desired button's `name` + * attribute. + */ + protected function submitFormByClickingButtonNamed($buttonName) + { + $page = $this->session->getPage(); + $button = $page->find('css', sprintf( + '[name=%s]', + $buttonName + )); + Assert::assertNotNull($button, 'Failed to find button named ' . $buttonName); + $button->click(); + $this->submitSecondarySspFormIfPresent($page); + } + + /** + * @Given I provide credentials that do not need review + */ + public function iProvideCredentialsThatDoNotNeedReview() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'no_review'; + $this->password = 'e'; + } + + /** + * @Given I provide credentials that are due for a(n) :category :nagType reminder + */ + public function iProvideCredentialsThatAreDueForAReminder($category, $nagType) + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = $category . '_' . $nagType; + switch ($this->username) { + case 'mfa_add': + $this->password = 'f'; + break; + + case 'method_add': + $this->password = 'g'; + break; + + case 'profile_review': + $this->password = 'h'; + break; + } + } + + + protected function pageContainsElementWithText($cssSelector, $text) + { + $page = $this->session->getPage(); + $elements = $page->findAll('css', $cssSelector); + foreach ($elements as $element) { + if (strpos($element->getText(), $text) !== false) { + return true; + } + } + return false; + } + + /** + * @Then there should be a way to continue to my intended destination + */ + public function thereShouldBeAWayToContinueToMyIntendedDestination() + { + $page = $this->session->getPage(); + $this->assertFormContains('name="continue"', $page); + } + + /** + * @When I click the remind-me-later button + */ + public function iClickTheRemindMeLaterButton() + { + $this->submitFormByClickingButtonNamed('continue'); + } + + /** + * @When I click the update profile button + */ + public function iClickTheUpdateProfileButton() + { + $this->submitFormByClickingButtonNamed('update'); + } + + /** + * @Then I should end up at the update profile URL + */ + public function iShouldEndUpAtTheUpdateProfileUrl() + { + $profileUrl = Env::get('PROFILE_URL_FOR_TESTS'); + Assert::assertNotEmpty($profileUrl, 'No PROFILE_URL_FOR_TESTS provided'); + $currentUrl = $this->session->getCurrentUrl(); + Assert::assertStringStartsWith( + $profileUrl, + $currentUrl, + 'Did NOT end up at the update profile URL' + ); + } + + /** + * @Then I should see the message: :message + */ + public function iShouldSeeTheMessage($message) + { + $page = $this->session->getPage(); + Assert::assertContains($message, $page->getHtml()); + } + + /** + * @Then there should be a way to go update my profile now + */ + public function thereShouldBeAWayToGoUpdateMyProfileNow() + { + $page = $this->session->getPage(); + $this->assertFormContains('name="update"', $page); + } + + /** + * @Given I provide credentials for a user that has used the manager mfa option + */ + public function iProvideCredentialsForAUserThatHasUsedTheManagerMfaOption() + { + // See `development/idp-local/config/authsources.php` for options. + $this->username = 'profile_review'; + $this->password = 'h'; + } + + /** + * @Then I should not see any manager mfa information + */ + public function iShouldNotSeeAnyManagerMfaInformation() + { + $page = $this->session->getPage(); + $isManagerMfaPresent = $page->hasContent('manager'); + Assert::assertFalse($isManagerMfaPresent, 'found manager mfa data'); + } +} diff --git a/features/bootstrap/SilDiscoContext.php b/features/bootstrap/SilDiscoContext.php new file mode 100644 index 00000000..24781840 --- /dev/null +++ b/features/bootstrap/SilDiscoContext.php @@ -0,0 +1,120 @@ +username = 'sildisco_idp1'; + $this->password = 'sildisco_password'; + break; + + case 'IDP 2': + $this->username = 'sildisco_idp2'; + $this->password = 'sildisco_password'; + break; + + case 'IDP 3': + $this->username = 'admin'; + $this->password = 'c'; + break; + + default: + throw new \Exception('credential name not recognized'); + } + $this->iLogIn(); + } + + /** + * @Then I should see my attributes on :sp + */ + public function iShouldSeeMyAttributesOnSp($sp) + { + $currentUrl = $this->session->getCurrentUrl(); + Assert::assertStringStartsWith( + 'http://ssp-' . strtolower($sp), + $currentUrl, + 'Did NOT end up at ' . $sp + ); + $this->assertPageContainsText('Your attributes'); + } + + /** + * @When I login using password :password + */ + public function iLoginUsingPassword($password) + { + $this->logInAs('admin', $password); + } + + /** + * @Given I have authenticated with IDP1 for :sp + */ + public function iHaveAuthenticatedWithIdp1($sp) + { + $this->iGoToTheSpLoginPage($sp); + $this->iClickOnTheTile('IDP 1'); + $this->username = 'sildisco_idp1'; + $this->password = 'sildisco_password'; + $this->iLogIn(); + } + + /** + * @Given I have authenticated with IDP2 for :sp + */ + public function iHaveAuthenticatedWithIdp2($sp) + { + $this->iGoToTheSpLoginPage($sp); + if ($sp != "SP2") { // SP2 only has IDP2 in its IDPList + $this->iClickOnTheTile('IDP 2'); + } + $this->username = 'sildisco_idp2'; + $this->password = 'sildisco_password'; + $this->iLogIn(); + } + + /** + * @When I log out of IDP1 + */ + public function iLogOutOfIdp1() + { + $this->iGoToTheSpLoginPage('SP3'); + $this->iClickOnTheTile('IDP 1'); + $this->clickLink('Logout'); + $this->assertPageContainsText('You have been logged out.'); + } + + /** + * @When I log out of IDP2 + */ + public function iLogOutOfIdp2() + { + $this->iGoToTheSpLoginPage('SP2'); + $this->clickLink('Logout'); + $this->assertPageContainsText('You have been logged out.'); + } + + /** + * @Then I should see the metadata in XML format + */ + public function iShouldSeeTheMetadataInXmlFormat() + { + $contentType = $this->session->getResponseHeader('Content-Type'); + Assert::assertEquals('application/xml', $contentType); + + Assert::assertEquals(200, $this->session->getStatusCode()); + + $xml = file_get_contents($this->getSession()->getCurrentUrl()); + Assert::assertStringContainsString( + 'entityID="ssp-hub.local"', + $xml, + "page doesn't contain entityID" + ); + } + +} diff --git a/features/bootstrap/StatusContext.php b/features/bootstrap/StatusContext.php new file mode 100644 index 00000000..a78c213a --- /dev/null +++ b/features/bootstrap/StatusContext.php @@ -0,0 +1,53 @@ +get('http://ssp-idp1.local/module.php/silauth/status.php'); + $this->responseCode = $response->getStatusCode(); + $this->responseText = $response->getBody()->getContents(); + } + + /** + * @Then I should get back a(n) :responseText with an HTTP status code of :statusCode + */ + public function iShouldGetBackAWithAnHttpStatusCodeOf($responseText, $statusCode) + { + Assert::same($this->responseText, $responseText); + Assert::eq($this->responseCode, $statusCode); + } + + /** + * @When I request the initial login page of this module + */ + public function iRequestTheInitialLoginPageOfThisModule() + { + $client = new Client([ + 'cookies' => true, + 'http_errors' => false, + ]); + $response = $client->get('http://ssp-idp1.local/module.php/core/authenticate.php?as=silauth'); + $this->responseCode = $response->getStatusCode(); + } + + /** + * @Then I should get back an HTTP status code of :statusCode + */ + public function iShouldGetBackAnHttpStatusCodeOf($statusCode) + { + Assert::eq($this->responseCode, $statusCode); + } +} diff --git a/features/expirychecker.feature b/features/expirychecker.feature index 80a9d51c..f93f7af4 100644 --- a/features/expirychecker.feature +++ b/features/expirychecker.feature @@ -2,15 +2,32 @@ Feature: Expiry Checker module Background: Given I go to the SP1 login page And I click on the "IDP 1" tile - - Scenario: Password is not about to expire - When I log in as a user who's password is NOT about to expire - Then I should see a page indicating that I successfully logged in - - Scenario: Password is about to expire - When I log in as a user who's password is about to expire - Then I should see a page warning me that my password is about to expire - + + Scenario: Password will expire in the distant future + Given I provide credentials that will expire in the distant future + When I log in + Then I should end up at my intended destination + + Scenario: Password will expire tomorrow + Given I provide credentials that will expire very soon + When I log in + Then I should see a warning that my password will expire soon + And there should be a way to go change my password now + And there should be a way to continue without changing my password + Scenario: Password has expired - When I log in as a user who's password has expired - Then I should see a page telling me that my password has expired + Given I provide credentials that have expired + When I log in + Then I should see a message that my password has expired + And there should be a way to go change my password now + But there should NOT be a way to continue without changing my password + + Scenario: Reject missing expiration date + Given I provide credentials that have no password expiration date + When I log in + Then I should see an error message + + Scenario: Reject invalid expiration date + Given I provide credentials that have an invalid password expiration date + When I log in + Then I should see an error message diff --git a/features/fakes/FakeIdBrokerClient.php b/features/fakes/FakeIdBrokerClient.php new file mode 100644 index 00000000..31899b3a --- /dev/null +++ b/features/fakes/FakeIdBrokerClient.php @@ -0,0 +1,136 @@ + $id, + 'type' => 'backupcode', + 'label' => 'Printable Codes', + 'created_utc' => '2019-01-02T03:04:05Z', + 'data' => [ + 'count' => 4, + ], + ]; + } + + /** + * Create a new MFA configuration + * @param string $employee_id + * @param string $type + * @param string $label + * @return array|null + * @throws Exception + */ + public function mfaCreate($employee_id, $type, $label = null) + { + if (empty($employee_id)) { + throw new InvalidArgumentException('employee_id is required'); + } + + if ($type === 'backupcode') { + return [ + "id" => 1234, + "data" => [ + "00000000", + "11111111", + "22222222", + "33333333", + "44444444", + "55555555", + "66666666", + "77777777", + "88888888", + "99999999" + ], + ]; + } + + if ($type === 'manager') { + return [ + "id" => 5678, + "data" => [], + ]; + } + + throw new InvalidArgumentException(sprintf( + 'This Fake ID Broker class does not support creating %s MFA records.', + $type + )); + } + + /** + * Get a list of MFA configurations for given user + * @param string $employee_id + * @return array + * @throws ServiceException + */ + public function mfaList($employee_id) + { + return [ + [ + 'id' => 1, + 'type' => 'backupcode', + 'label' => 'Printable Codes', + 'created_utc' => '2019-04-02T16:02:14Z', + 'last_used_utc' => '2019-04-01T00:00:00Z', + 'data' => [ + 'count' => 10 + ], + ], + [ + 'id' => 2, + 'type' => 'totp', + 'label' => 'Smartphone App', + 'created_utc' => '2019-04-02T16:02:14Z', + 'last_used_utc' => '2019-04-01T00:00:00Z', + 'data' => [ + ], + ], + ]; + } +} diff --git a/features/login.feature b/features/login.feature new file mode 100644 index 00000000..77e9a2d8 --- /dev/null +++ b/features/login.feature @@ -0,0 +1,207 @@ +Feature: User login + In order to log in + As a user + I need to provide an acceptable username and password + + Rules + - Username and password are both required. + - The user is only allowed through if all of the necessary checks pass. + + Scenario: Failing to provide a username + Given I provide a password + But I do not provide a username + When I try to log in + Then I should see an error message with "username" in it + And I should not be allowed through + + Scenario: Failing to provide a password + Given I provide a username + But I do not provide a password + When I try to log in + Then I should see an error message with "password" in it + And I should not be allowed through + + Scenario: Enough failed logins to require a captcha for a username + Given I provide a username + And I provide the correct password for that username + But that username has enough failed logins to require a captcha + And I fail the captcha + When I try to log in + Then I should see a generic invalid-login error message + And I should not be allowed through + + Scenario: Enough failed logins to require a captcha for an IP address + Given my request comes from IP address "11.22.33.44" + And I provide a username + And I provide the correct password for that username + And that username does not have enough failed logins to require a captcha + But my IP address has enough failed logins to require a captcha + And I fail the captcha + When I try to log in + Then I should see a generic invalid-login error message + And I should not be allowed through + + Scenario: Trying to log in with a rate-limited username + Given I provide a username + And I provide a password + But that username has enough failed logins to be blocked by the rate limit + When I try to log in + Then I should see an error message telling me to wait + And that username should be blocked for awhile + And I should not be allowed through + + Scenario: Trying to log in with a rate-limited IP address + Given I provide a username + And I provide a password + And my request comes from IP address "11.22.33.44" + And that IP address has triggered the rate limit + When I try to log in + Then I should see an error message telling me to wait + And that IP address should be blocked for awhile + And I should not be allowed through + + Scenario: Providing unacceptable credentials + Given I provide a username + And that username has no recent failed login attempts + But I provide an incorrect password + When I try to log in + Then I should see a generic invalid-login error message + And I should not be allowed through + + Scenario: Providing unacceptable credentials that trigger a rate limit + Given I provide a username + And that username will be rate limited after one more failed attempt + And I pass the captcha + But I provide an incorrect password + When I try to log in + Then I should see an error message telling me to wait + And that username should be blocked for awhile + And I should not be allowed through + + Scenario: Providing a correct username-password combination + Given I provide a username + And I provide the correct password for that username + And that username has no recent failed login attempts + When I try to log in + Then I should not see an error message + And I should be allowed through + + Scenario: Providing too many incorrect username-password combinations + Given I provide a username + And I provide an incorrect password + And I pass any captchas + When I try to log in enough times to trigger the rate limit + Then I should see an error message telling me to wait + And that username should be blocked for awhile + And I should not be allowed through + + Scenario: Providing correct credentials after one failed login attempt + Given I provide a username + And I provide an incorrect password + And I try to log in + But I then provide the correct password for that username + When I try to log in + Then I should not see an error message + And I should be allowed through + And that username's failed login attempts should be at 0 + + Scenario: Being told about how long to wait (due to rate limiting bad logins) + Given I provide a username + And I provide the correct password for that username + But that username has 5 more recent failed logins than the limit + When I try to log in + Then I should see an error message with "30" and "seconds" in it + And that username should be blocked for awhile + And I should not be allowed through + + Scenario: Logging in right before a username rate limit has expired + Given I provide a username + And I provide the correct password for that username + And 24 seconds ago that username had 5 more failed logins than the limit + And I pass any captchas + When I try to log in + Then I should see an error message with "5" and "seconds" in it + And I should not be allowed through + + Scenario: Logging in right after a username rate limit has expired + Given I provide a username + And I provide the correct password for that username + And 26 seconds ago that username had 5 more failed logins than the limit + And I pass any captchas + When I try to log in + Then I should not see an error message + And I should be allowed through + + Scenario: Logging in right before an IP address rate limit has expired + Given my request comes from IP address "11.22.33.44" + And I provide a username + And I provide the correct password for that username + And 24 seconds ago the IP address "11.22.33.44" had 5 more failed logins than the limit + And I pass any captchas + When I try to log in + Then I should see an error message with "5" and "seconds" in it + And I should not be allowed through + + Scenario: Logging in right after an IP address rate limit has expired + Given my request comes from IP address "11.22.33.44" + And I provide a username + And I provide the correct password for that username + And 26 seconds ago the IP address "11.22.33.44" had 5 more failed logins than the limit + And I pass any captchas + When I try to log in + Then I should not see an error message + And I should be allowed through + + Scenario: No failed logins (and thus no captcha requirement) + Given I provide a username + When that username has no recent failed login attempts + Then I should not have to pass a captcha test for that user + + Scenario: Not restricting requests from a trusted IPv4 address + Given I provide a username + And I provide an incorrect password + And my request comes from IP address "11.22.33.44" + But "11.22.33.44" is a trusted IP address + When I try to log in + Then I should see a generic invalid-login error message + And I should not be allowed through + But the IP address "11.22.33.44" should not have any failed login attempts + + Scenario: Not restricting requests from a trusted IPv6 address + Given I provide a username + And I provide an incorrect password + And my request comes from IP address "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + But "2001:0db8:85a3::8a2e:0370:7334" is a trusted IP address + When I try to log in + Then I should see a generic invalid-login error message + And I should not be allowed through + But the IP address "2001:0db8:85a3:0000:0000:8a2e:0370:7334" should not have any failed login attempts + + Scenario: Not restricting requests from an IP address in a trusted range + Given I provide a username + And I provide an incorrect password + And my request comes from IP address "11.22.33.44" + But "11.22.33.0/24" is a trusted IP address range + When I try to log in + Then I should see a generic invalid-login error message + And I should not be allowed through + But the IP address "11.22.33.44" should not have any failed login attempts + + Scenario: Restricting requests from an IP address NOT in a trusted range + Given I provide a username + And I provide an incorrect password + And my request comes from IP address "11.22.33.44" + And "11.22.55.0/24" is a trusted IP address range + When I try to log in + Then I should see a generic invalid-login error message + And I should not be allowed through + And the IP address "11.22.33.44" should have a failed login attempt + + Scenario: Invalid response from the ID Broker + Given I provide a username + And I provide the correct password for that username + And that username has no recent failed login attempts + But the ID Broker is returning invalid responses + When I try to log in + Then I should see a generic try-later error message + And I should not be allowed through diff --git a/features/material.feature b/features/material.feature index 9ed643bb..78da2688 100644 --- a/features/material.feature +++ b/features/material.feature @@ -21,8 +21,8 @@ Feature: Material theme Scenario: Login page When I go to the SP1 login page - And I click on the "IDP 1" tile - Then I should see a "Login with your IDP 1 identity" page + And I click on the "IDP 2" tile + Then I should see a "Login with your IDP 2 identity" page And I should see our material theme Scenario: Forgot password link diff --git a/features/mfa.feature b/features/mfa.feature index 2700429b..5a9c948f 100644 --- a/features/mfa.feature +++ b/features/mfa.feature @@ -1,21 +1,252 @@ -Feature: Multi-Factor Authentication (MFA) module - - Scenario: Low on backup codes - - Scenario: Must set up MFA - - Scenario: New backup codes - - Scenario: Other MFAs - - Scenario: Out of backup codes - - Scenario: Prompt for MFA (backup code) - - Scenario: Prompt for MFA (manager) - - Scenario: Prompt for MFA (TOTP) - - Scenario: Prompt for MFA (U2F) - - Scenario: Send manager MFA +Feature: Prompt for MFA credentials + + Background: + Given I go to the SP1 login page + And I click on the "IDP 1" tile + + Scenario: Don't prompt for MFA + Given I provide credentials that do not need MFA + When I log in + Then I should end up at my intended destination + + Scenario: Needs MFA, but no MFA options are available + Given I provide credentials that need MFA but have no MFA options available + When I log in + Then I should see a message that I have to set up MFA + And there should be a way to go set up MFA now + And there should NOT be a way to continue to my intended destination + + Scenario: Following the requirement to go set up MFA + Given I provide credentials that need MFA but have no MFA options available + And I log in + When I click the set-up-MFA button + Then I should end up at the mfa-setup URL + And I should NOT be able to get to my intended destination + + Scenario: Needs MFA, has backup code option available + Given I provide credentials that need MFA and have backup codes available + When I log in + Then I should see a prompt for a backup code + + Scenario: Needs MFA, has TOTP option available + Given I provide credentials that need MFA and have TOTP available + When I log in + Then I should see a prompt for a TOTP code + + Scenario: Needs MFA, has WebAuthn option available + Given I provide credentials that need MFA and have WebAuthn available + And the user's browser supports WebAuthn + When I log in + Then I should see a prompt for a WebAuthn security key + + Scenario: Accepting a (non-rate-limited) correct MFA value + Given I provide credentials that need MFA and have backup codes available + And I have logged in + When I submit a correct backup code + Then I should end up at my intended destination + + Scenario: Rejecting a (non-rate-limited) wrong MFA value + Given I provide credentials that need MFA and have backup codes available + And I have logged in + When I submit an incorrect backup code + Then I should see a message that it was incorrect + + Scenario: Blocking an incorrect MFA value while rate-limited + Given I provide credentials that have a rate-limited MFA + And I have logged in + When I submit an incorrect backup code + Then I should see a message that I have to wait before trying again + + Scenario: Blocking a correct MFA value while rate-limited + Given I provide credentials that have a rate-limited MFA + And I have logged in + When I submit a correct backup code + Then I should see a message that I have to wait before trying again + + Scenario: Warning when running low on backup codes + Given I provide credentials that need MFA and have 4 backup codes available + And I have logged in + When I submit a correct backup code + Then I should see a message that I am running low on backup codes + And I should be told I only have 3 backup codes left + And there should be a way to get more backup codes now + And there should be a way to continue to my intended destination + + Scenario: Requiring user to set up more backup codes when they run out and have no other MFA + Given I provide credentials that need MFA and have 1 backup code available and no other MFA + And I have logged in + When I submit a correct backup code + Then I should see a message that I have used up my backup codes + And there should be a way to get more backup codes now + And there should NOT be a way to continue to my intended destination + + Scenario: Warning user when they run out of backup codes but have other MFA options + Given I provide credentials that need MFA and have 1 backup code available plus some other MFA + And I have logged in + When I submit a correct backup code + Then I should see a message that I have used up my backup codes + And there should be a way to get more backup codes now + And there should be a way to continue to my intended destination + + Scenario: Obeying the nag to set up more backup codes when low + Given I provide credentials that need MFA and have 4 backup codes available + And I have logged in + And I submit a correct backup code + When I click the get-more-backup-codes button + Then I should be given more backup codes + And there should be a way to continue to my intended destination + + Scenario: Ignoring the nag to set up more backup codes when low + Given I provide credentials that need MFA and have 4 backup codes available + And I have logged in + And I submit a correct backup code + When I click the remind-me-later button + Then I should end up at my intended destination + + Scenario: Obeying the requirement to set up more backup codes when out + Given I provide credentials that need MFA and have 1 backup code available and no other MFA + And I have logged in + And I submit a correct backup code + When I click the get-more-backup-codes button + Then I should be given more backup codes + And there should be a way to continue to my intended destination + + Scenario: Obeying the nag to set up more backup codes when out + Given I provide credentials that need MFA and have 1 backup code available plus some other MFA + And I have logged in + And I submit a correct backup code + When I click the get-more-backup-codes button + Then I should be given more backup codes + And there should be a way to continue to my intended destination + + Scenario: Ignoring the nag to set up more backup codes when out + Given I provide credentials that need MFA and have 1 backup code available plus some other MFA + And I have logged in + And I submit a correct backup code + When I click the remind-me-later button + Then I should end up at my intended destination + + Scenario Outline: Defaulting to another option when WebAuthn is not supported + Given I provide credentials that have + And the user's browser + When I log in + Then I should see a prompt for a + + Examples: + | WebAuthn? | TOTP? | backup codes? | supports WebAuthn or not | default MFA type | + | WebAuthn | | | supports WebAuthn | WebAuthn | + | WebAuthn | , TOTP | | supports WebAuthn | WebAuthn | + | WebAuthn | | , backup codes | supports WebAuthn | WebAuthn | + | WebAuthn | , TOTP | , backup codes | supports WebAuthn | WebAuthn | + | | TOTP | | supports WebAuthn | TOTP | + | | TOTP | , backup codes | supports WebAuthn | TOTP | + | | | backup codes | supports WebAuthn | backup code | +# The following cases are disabled due to lack of test support for changing web client user agent +# | WebAuthn | | | does not support WebAuthn | WebAuthn | +# | WebAuthn | , TOTP | | does not support WebAuthn | TOTP | +# | WebAuthn | | , backup codes | does not support WebAuthn | backup code | +# | WebAuthn | , TOTP | , backup codes | does not support WebAuthn | TOTP | +# | | TOTP | | does not support WebAuthn | TOTP | +# | | TOTP | , backup codes | does not support WebAuthn | TOTP | +# | | | backup codes | does not support WebAuthn | backup code | + + + Scenario Outline: Defaulting to the most recently used mfa option + Given I provide credentials that have a used + And and I have a more recently used + And the user's browser + When I log in + Then I should see a prompt for a + + Examples: + | MFA type | recent MFA type | supports WebAuthn or not | default MFA type | + | WebAuthn | TOTP | supports WebAuthn | TOTP | + | TOTP | WebAuthn | supports WebAuthn | WebAuthn | + | TOTP | backup code | supports WebAuthn | backup code | + | backup code | TOTP | supports WebAuthn | TOTP | +# The following case is disabled due to lack of test support for changing web client user agent +# | TOTP | WebAuthn | does not support WebAuthn | TOTP | + + Scenario: Defaulting to the manager code despite having a used mfa + Given I provide credentials that have a manager code, a WebAuthn and a more recently used TOTP + And the user's browser supports WebAuthn + When I log in + Then I should see a prompt for a manager rescue code + + Scenario Outline: When to show the WebAuthn-not-supported error message + Given I provide credentials that have WebAuthn + And the user's browser + When I log in + Then I see an error message about WebAuthn being unsupported + + Examples: + | supports WebAuthn or not | should or not | + | supports WebAuthn | should not | +# The following case is disabled due to lack of test support for changing web client user agent +# | does not support WebAuthn | should | + + Scenario Outline: When to show the link to send a manager rescue code + Given I provide credentials that have + And the user a manager email + When I log in + Then I see a link to send a code to the user's manager + + Examples: + | WebAuthn? | TOTP? | backup codes? | has or does not have | should or should not | + | WebAuthn | | | has | should | + | WebAuthn | , TOTP | | has | should | + | WebAuthn | | , backup codes | has | should | + | WebAuthn | , TOTP | , backup codes | has | should | + | | TOTP | | has | should | + | | TOTP | , backup codes | has | should | + | | | backup codes | has | should | + | WebAuthn | | | does not have | should not | + | WebAuthn | , TOTP | | does not have | should not | + | WebAuthn | | , backup codes | does not have | should not | + | WebAuthn | , TOTP | , backup codes | does not have | should not | + | | TOTP | | does not have | should not | + | | TOTP | , backup codes | does not have | should not | + | | | backup codes | does not have | should not | + + Scenario: Ask for a code to be sent to my manager + Given I provide credentials that have backup codes + And the user has a manager email + And I log in + When I click the Request Assistance link + Then there should be a way to request a manager code + + Scenario: Submit a code sent to my manager at an earlier time + Given I provide credentials that have a manager code + And I log in + When I submit the correct manager code + # because profile review is required after using a manager code: + And I click the remind-me-later button + Then I should end up at my intended destination + + Scenario: Submit a correct manager code + Given I provide credentials that have backup codes + And the user has a manager email + And I log in + And I click the Request Assistance link + And I click the Send a code link + When I submit the correct manager code + # because profile review is required after using a manager code: + And I click the remind-me-later button + Then I should end up at my intended destination + + Scenario: Submit an incorrect manager code + Given I provide credentials that have backup codes + And the user has a manager email + And I log in + And I click the Request Assistance link + And I click the Send a code link + When I submit an incorrect manager code + Then I should see a message that it was incorrect + + Scenario: Ask for assistance, but change my mind + Given I provide credentials that have backup codes + And the user has a manager email + And I log in + And I click the Request Assistance link + When I click the Cancel button + Then I should see a prompt for a backup code diff --git a/features/profilereview.feature b/features/profilereview.feature index dbb843e5..9c3988d1 100644 --- a/features/profilereview.feature +++ b/features/profilereview.feature @@ -1,7 +1,52 @@ -Feature: Profile review module - - Scenario: Nag user about having no MFA - - Scenario: Nag user about having no password recovery methods - - Scenario: Review user's profile +Feature: Prompt to review profile information + Background: + Given I go to the SP1 login page + And I click on the "IDP 1" tile + + Scenario: Don't ask for review + Given I provide credentials that do not need review + When I log in + Then I should end up at my intended destination + + Scenario Outline: Present reminder as required by the user profile + Given I provide credentials that are due for a reminder + When I log in + Then I should see the message: + And there should be a way to go update my profile now + And there should be a way to continue to my intended destination + + Examples: + | category | nag type | message | + | mfa | add | "2-Step Verification" | + | method | add | "alternate email addresses" | + | profile | review | "Please take a moment to review" | + + Scenario Outline: Obeying a reminder + Given I provide credentials that are due for a reminder + And I have logged in + When I click the update profile button + Then I should end up at the update profile URL + + Examples: + | category | nag type | + | mfa | add | + | method | add | + | profile | review | + + Scenario Outline: Ignoring a reminder + Given I provide credentials that are due for a reminder + And I have logged in + When I click the remind-me-later button + Then I should end up at my intended destination + + Examples: + | category | nag type | + | mfa | add | + | method | add | + | profile | review | + + Scenario: Ensuring that manager mfa data is not displayed to the user + Given I provide credentials for a user that has used the manager mfa option + And I have logged in + Then I should see the message: "Please take a moment to review" + And I should not see any manager mfa information diff --git a/features/status.feature b/features/status.feature new file mode 100644 index 00000000..660997e2 --- /dev/null +++ b/features/status.feature @@ -0,0 +1,9 @@ +Feature: Status check + + Scenario: Good status check + When I check the status of this module + Then I should get back an "OK" with an HTTP status code of "200" + + Scenario: Request initial login page + When I request the initial login page of this module + Then I should get back an HTTP status code of "200" diff --git a/local.broker.env.dist b/local.broker.env.dist new file mode 100755 index 00000000..59eca0a9 --- /dev/null +++ b/local.broker.env.dist @@ -0,0 +1,18 @@ + +### Required ENV vars ### +MFA_TOTP_apiBaseUrl= +MFA_TOTP_apiKey= +MFA_TOTP_apiSecret= + +MFA_WEBAUTHN_apiBaseUrl= +MFA_WEBAUTHN_apiKey= +MFA_WEBAUTHN_apiSecret= +MFA_WEBAUTHN_appId= +MFA_WEBAUTHN_rpDisplayName= +MFA_WEBAUTHN_rpId= + +# Comma separated array of origins allowed as relying parties (with scheme, without port or path) +RP_ORIGINS= + +### Optional ENV vars ### +COMPOSER_AUTH="{\"github-oauth\": {\"github.com\": \"YOUR TOKEN HERE\"}}" diff --git a/local.env.dist b/local.env.dist index c847edfd..9f8fd47b 100644 --- a/local.env.dist +++ b/local.env.dist @@ -59,3 +59,36 @@ SHOW_SAML_ERRORS= THEME_USE= TIMEZONE= XDEBUG_REMOTE_HOST= + +# expirychecker config + +# The URL to send a user to for changing their password. +# Example: https://pw.example.com/#/password/create +PASSWORD_CHANGE_URL= + +# profilereview config + +# The URL to send a user to for setting up their profile. +# Example: https://pw.example.com/#/ +PROFILE_URL= + +# An absolute URL the user can go to to learn more about MFA. +MFA_LEARN_MORE_URL= + +# MFA config + +# The URL to send a user to for setting up their MFA. +# Example: https://pw.example.com/#/2sv/intro +MFA_SETUP_URL= + +# silauth config + +# List any IP addresses and/or IP address ranges (CIDR) that should NOT be +# rate-limited but which might be included in REMOTE_ADDR or the X-Forwarded-For +# header (such as your application's load balancer). +# Example: TRUSTED_IP_ADDRESSES=11.22.33.44,11.22.55.0/24 +TRUSTED_IP_ADDRESSES= + +# See "https://developers.google.com/recaptcha/docs/faq" for test key/secret. +RECAPTCHA_SITE_KEY= +RECAPTCHA_SECRET= diff --git a/modules/expirychecker/lib/Auth/Process/ExpiryDate.php b/modules/expirychecker/lib/Auth/Process/ExpiryDate.php new file mode 100644 index 00000000..0275dffb --- /dev/null +++ b/modules/expirychecker/lib/Auth/Process/ExpiryDate.php @@ -0,0 +1,410 @@ +initLogger($config); + + $this->loadValuesFromConfig($config, [ + 'warnDaysBefore' => [ + Validator::INT, + ], + 'originalUrlParam' => [ + Validator::STRING, + Validator::NOT_EMPTY, + ], + 'passwordChangeUrl' => [ + Validator::STRING, + Validator::NOT_EMPTY, + ], + 'accountNameAttr' => [ + Validator::STRING, + Validator::NOT_EMPTY, + ], + 'expiryDateAttr' => [ + Validator::STRING, + Validator::NOT_EMPTY, + ], + 'dateFormat' => [ + Validator::STRING, + Validator::NOT_EMPTY, + ], + ]); + } + + protected function loadValuesFromConfig($config, $attributeRules) + { + foreach ($attributeRules as $attribute => $rules) { + if (array_key_exists($attribute, $config)) { + $this->$attribute = $config[$attribute]; + } + + Validator::validate($this->$attribute, $rules, $this->logger, $attribute); + } + } + + /** + * Get the specified attribute from the given state data. + * + * NOTE: If the attribute's data is an array, the first value will be + * returned. Otherwise, the attribute's data will simply be returned + * as-is. + * + * @param string $attributeName The name of the attribute. + * @param array $state The state data. + * @return mixed The attribute value, or null if not found. + */ + protected function getAttribute($attributeName, $state) + { + $attributeData = $state['Attributes'][$attributeName] ?? null; + + if (is_array($attributeData)) { + return $attributeData[0] ?? null; + } + + return $attributeData; + } + + /** + * Calculate how many days remain between now and when the password will + * expire. + * + * @param int $expiryTimestamp The timestamp for when the password will + * expire. + * @return int The number of days remaining + */ + protected function getDaysLeftBeforeExpiry($expiryTimestamp) + { + $now = time(); + $end = $expiryTimestamp; + return round(($end - $now) / (24*60*60)); + } + + /** + * Get the timestamp for when the user's password will expire, throwing an + * exception if unable to do so. + * + * @param string $expiryDateAttr The name of the attribute where the + * expiration date (as a string) is stored. + * @param array $state The state data. + * @return int The expiration timestamp. + * @throws \Exception + */ + protected function getExpiryTimestamp($expiryDateAttr, $state) + { + $expiryDateString = $this->getAttribute($expiryDateAttr, $state); + + // Ensure that EVERY user login provides a usable password expiration date. + $expiryTimestamp = strtotime($expiryDateString) ?: null; + if (empty($expiryTimestamp)) { + throw new \Exception(sprintf( + "We could not understand the expiration date (%s, from %s) for " + . "the user's password, so we do not know whether their " + . "password is still valid.", + var_export($expiryDateString, true), + var_export($expiryDateAttr, true) + ), 1496843359); + } + return $expiryTimestamp; + } + + public static function hasSeenSplashPageRecently() + { + $session = Session::getSessionFromRequest(); + return (bool)$session->getData( + self::SESSION_TYPE, + self::HAS_SEEN_SPLASH_PAGE + ); + } + + public static function skipSplashPagesFor($seconds) + { + $session = Session::getSessionFromRequest(); + $session->setData( + self::SESSION_TYPE, + self::HAS_SEEN_SPLASH_PAGE, + true, + $seconds + ); + $session->save(); + } + + protected function initLogger($config) + { + $loggerClass = $config['loggerClass'] ?? Psr3SamlLogger::class; + $this->logger = new $loggerClass(); + if (! $this->logger instanceof LoggerInterface) { + throw new \Exception(sprintf( + 'The specified loggerClass (%s) does not implement ' + . '\\Psr\\Log\\LoggerInterface.', + var_export($loggerClass, true) + ), 1496928725); + } + } + + /** + * See if the given timestamp is in the past. + * + * @param int $timestamp The timestamp to check. + * @return bool + */ + public function isDateInPast(int $timestamp) + { + return ($timestamp < time()); + } + + /** + * Check whether the user's password has expired. + * + * @param int $expiryTimestamp The timestamp for when the user's password + * will expire. + * @return bool + */ + public function isExpired(int $expiryTimestamp) + { + return $this->isDateInPast($expiryTimestamp); + } + + /** + * Check whether it's time to warn the user that they will need to change + * their password soon. + * + * @param int $expiryTimestamp The timestamp for when the password expires. + * @param int $warnDaysBefore How many days before the expiration we should + * warn the user. + * @return boolean + */ + public function isTimeToWarn($expiryTimestamp, $warnDaysBefore) + { + $daysLeft = $this->getDaysLeftBeforeExpiry($expiryTimestamp); + return ($daysLeft <= $warnDaysBefore); + } + + /** + * Redirect the user to the change password url if they haven't gone + * there in the last 10 minutes + * @param array $state + * @param string $accountName + * @param string $passwordChangeUrl + * @param string $change_pwd_session + * @param int $expiryTimestamp The timestamp when the password will expire. + */ + public function redirect2PasswordChange( + &$state, + $accountName, + $passwordChangeUrl, + $change_pwd_session, + $expiryTimestamp + ) { + $sessionType = 'expirychecker'; + /* Save state and redirect. */ + $state['expiresAtTimestamp'] = $expiryTimestamp; + $state['accountName'] = $accountName; + $id = State::saveState( + $state, + 'expirychecker:redirected_to_password_change_url' + ); + $ignoreMinutes = 60; + + $session = Session::getSessionFromRequest(); + $idpExpirySession = $session->getData($sessionType, $change_pwd_session); + + // If the session shows that the User already passed this way, + // don't redirect to change password page + if ($idpExpirySession !== null) { + ProcessingChain::resumeProcessing($state); + } else { + // Otherwise, set a value to tell us they've probably changed + // their password, in order to allow password to get propagated + $session->setData( + $sessionType, + $change_pwd_session, + 1, + (60 * $ignoreMinutes) + ); + $session->save(); + } + + + /* If state already has the change password url, go straight there to + * avoid eternal loop between that and the idp. Otherwise add the + * original destination url as a parameter. */ + if (array_key_exists('saml:RelayState', $state)) { + $relayState = $state['saml:RelayState']; + if (strpos($relayState, $passwordChangeUrl) !== false) { + ProcessingChain::resumeProcessing($state); + } else { + $returnTo = Utilities::getUrlFromRelayState( + $relayState + ); + if (! empty($returnTo)) { + $passwordChangeUrl .= '?returnTo=' . $returnTo; + } + } + } + + $this->logger->warning(json_encode([ + 'event' => 'expirychecker: redirecting to change password', + 'accountName' => $accountName, + 'passwordChangeUrl' => $passwordChangeUrl, + ])); + + HTTP::redirectTrustedURL($passwordChangeUrl, array()); + } + + /** + * Apply this AuthProc Filter. + * + * @param array &$state The current state. + */ + public function process(&$state) + { + $employeeId = $this->getAttribute($this->employeeIdAttr, $state); + + /* If the user has already seen a splash page from this AuthProc + * recently, simply let them pass on through (so they can get into the + * change-password website, for example). */ + if (self::hasSeenSplashPageRecently()) { + $this->logger->warning(json_encode([ + 'event' => 'expirychecker: skip message, seen recently', + 'employeeId' => $employeeId, + ])); + return; + } + + // Get the necessary info from the state data. + $accountName = $this->getAttribute($this->accountNameAttr, $state); + $expiryTimestamp = $this->getExpiryTimestamp($this->expiryDateAttr, $state); + + $this->logger->warning(json_encode([ + 'event' => 'expirychecker: will check expiration date', + 'employeeId' => $employeeId, + 'accountName' => $accountName, + 'expiryDateAttrValue' => $this->getAttribute($this->expiryDateAttr, $state), + 'expiryTimestamp' => $expiryTimestamp, + ])); + + if ($this->isExpired($expiryTimestamp)) { + $this->redirectToExpiredPage($state, $accountName, $expiryTimestamp); + } + + // Display a password expiration warning page if it's time to do so. + if ($this->isTimeToWarn($expiryTimestamp, $this->warnDaysBefore)) { + $this->redirectToWarningPage($state, $accountName, $expiryTimestamp); + } + + $this->logger->warning(json_encode([ + 'event' => 'expirychecker: no action necessary', + 'employeeId' => $employeeId, + ])); + } + + /** + * Redirect the user to the expired-password page. + * + * @param array $state The state data. + * @param string $accountName The name of the user account. + * @param int $expiryTimestamp When the password expired. + */ + public function redirectToExpiredPage(&$state, $accountName, $expiryTimestamp) + { + assert('is_array($state)'); + + $this->logger->warning(json_encode([ + 'event' => 'expirychecker: password expired', + 'accountName' => $accountName, + ])); + + /* Save state and redirect. */ + $state['expiresAtTimestamp'] = $expiryTimestamp; + $state['accountName'] = $accountName; + $state['passwordChangeUrl'] = $this->passwordChangeUrl; + $state['originalUrlParam'] = $this->originalUrlParam; + + $id = State::saveState($state, 'expirychecker:expired'); + $url = Module::getModuleURL('expirychecker/expired.php'); + + HTTP::redirectTrustedURL($url, array('StateId' => $id)); + } + + /** + * Redirect the user to the warning page. + * + * @param array $state The state data. + * @param string $accountName The name of the user account. + * @param int $expiryTimestamp When the password will expire. + */ + protected function redirectToWarningPage(&$state, $accountName, $expiryTimestamp) + { + assert('is_array($state)'); + + $this->logger->warning(json_encode([ + 'event' => 'expirychecker: about to expire', + 'accountName' => $accountName, + ])); + + $daysLeft = $this->getDaysLeftBeforeExpiry($expiryTimestamp); + $state['daysLeft'] = $daysLeft; + + if (isset($state['isPassive']) && $state['isPassive'] === true) { + /* We have a passive request. Skip the warning. */ + return; + } + + /* Save state and redirect. */ + $state['expiresAtTimestamp'] = $expiryTimestamp; + $state['accountName'] = $accountName; + $state['passwordChangeUrl'] = $this->passwordChangeUrl; + $state['originalUrlParam'] = $this->originalUrlParam; + + $id = State::saveState($state, 'expirychecker:about2expire'); + $url = Module::getModuleURL('expirychecker/about2expire.php'); + + HTTP::redirectTrustedURL($url, array('StateId' => $id)); + } +} diff --git a/modules/expirychecker/lib/Utilities.php b/modules/expirychecker/lib/Utilities.php new file mode 100644 index 00000000..0e2b80b6 --- /dev/null +++ b/modules/expirychecker/lib/Utilities.php @@ -0,0 +1,92 @@ +critical($exception->getMessage()); + throw $exception; + } + } + } + + /** + * See if the given value satisfies the specified rule. + * + * @param mixed $value The value to check. + * @param string $rule The rule (see this class's constants). + * @param LoggerInterface $logger The logger. + * @return bool + * @throws InvalidArgumentException + */ + protected static function isValid($value, $rule, $logger) + { + switch ($rule) { + case self::INT: + return is_int($value); + + case self::NOT_EMPTY: + return !empty($value); + + case self::STRING: + return is_string($value); + + default: + $exception = new InvalidArgumentException(sprintf( + 'Unknown validation rule: %s', + var_export($rule, true) + ), 1496866914); + + $logger->critical($exception->getMessage()); + throw $exception; + } + } +} diff --git a/modules/expirychecker/templates/about2expire.php b/modules/expirychecker/templates/about2expire.php new file mode 100644 index 00000000..468faeaf --- /dev/null +++ b/modules/expirychecker/templates/about2expire.php @@ -0,0 +1,48 @@ +data['header'] = sprintf( + 'Your password will expire in %s %s', + $this->data['daysLeft'], + $this->data['dayOrDays'] +); +$this->data['autofocus'] = 'yesbutton'; + +$this->includeAtTemplateBase('includes/header.php'); + +$dateString = msgfmt_format_message( + $this->getLanguage(), + '{0,date,long}', + [$this->data['expiresAtTimestamp']] +); + +?> +

+ The password for your data['accountName']); ?> + account will expire on . +

+

+ Would you like to update your password now? +

+ +
+ + data['formData'] as $name => $value): ?> + + + + + + +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/expirychecker/templates/expired.php b/modules/expirychecker/templates/expired.php new file mode 100644 index 00000000..8b46d306 --- /dev/null +++ b/modules/expirychecker/templates/expired.php @@ -0,0 +1,38 @@ +data['header'] = 'Your password has expired'; + +$this->includeAtTemplateBase('includes/header.php'); + +$dateString = msgfmt_format_message( + $this->getLanguage(), + '{0,date,long}', + [$this->data['expiresAtTimestamp']] +); + +?> +

+ The password for your data['accountName']); ?> + account expired on . +

+

+ You will need to update your password before you can continue to where you + were going. +

+

+

+ + data['formData'] as $name => $value): ?> + + + + +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/expirychecker/www/about2expire.php b/modules/expirychecker/www/about2expire.php new file mode 100644 index 00000000..9e38ac03 --- /dev/null +++ b/modules/expirychecker/www/about2expire.php @@ -0,0 +1,65 @@ +data['formTarget'] = Module::getModuleURL('expirychecker/about2expire.php'); +$t->data['formData'] = ['StateId' => $stateId]; +$t->data['daysLeft'] = $state['daysLeft']; +$t->data['dayOrDays'] = (intval($state['daysLeft']) === 1 ? 'day' : 'days'); +$t->data['expiresAtTimestamp'] = $state['expiresAtTimestamp']; +$t->data['accountName'] = $state['accountName']; +$t->show(); + +Logger::info('expirychecker - User has been warned that their password will expire soon.'); diff --git a/modules/expirychecker/www/expired.php b/modules/expirychecker/www/expired.php new file mode 100644 index 00000000..c4c0b8c9 --- /dev/null +++ b/modules/expirychecker/www/expired.php @@ -0,0 +1,56 @@ +data['formTarget'] = Module::getModuleURL('expirychecker/expired.php'); +$t->data['formData'] = ['StateId' => $stateId]; +$t->data['expiresAtTimestamp'] = $state['expiresAtTimestamp']; +$t->data['accountName'] = $state['accountName']; +$t->show(); + +Logger::info('expirychecker - User has been told that their password has expired.'); diff --git a/modules/material/dictionaries/about2expire.definition.json b/modules/material/dictionaries/about2expire.definition.json new file mode 100644 index 00000000..b39acd50 --- /dev/null +++ b/modules/material/dictionaries/about2expire.definition.json @@ -0,0 +1,45 @@ + +{ + "title": { + "en": "Expiring password", + "es": "Contraseña vencida", + "fr": "Mot de passe expiré", + "ko": "만료 된 암호" + }, + "header": { + "en": "Password expiring soon", + "es": "Contraseña caducada pronto", + "fr": "Mot de passe expire bientôt", + "ko": "곧 만료되는 암호" + }, + "expiring_in_a_day": { + "en": "Your password will expire in one day.", + "es": "Su contraseña caducará en un día.", + "fr": "Votre mot de passe expirera en un jour.", + "ko": "암호는 하루 만료됩니다." + }, + "expiring_soon": { + "en": "Your password will expire in {daysLeft} days.", + "es": "Su contraseña caducará en {daysLeft} días.", + "fr": "Votre mot de passe expirera en {daysLeft} jours.", + "ko": "비밀번호는 {daysLeft} 일 후에 만료됩니다." + }, + "change_now": { + "en": "Would you like to change it now?", + "es": "¿Quieres cambiarlo ahora?", + "fr": "Voulez-vous le changer maintenant?", + "ko": "지금 변경 하시겠습니까?" + }, + "button_change": { + "en": "Yes", + "es": "Sí", + "fr": "Oui", + "ko": "예" + }, + "button_continue": { + "en": "Later", + "es": "Después", + "fr": "Plus tard", + "ko": "후에" + } +} diff --git a/modules/material/dictionaries/error.definition.json b/modules/material/dictionaries/error.definition.json new file mode 100644 index 00000000..1cff9bba --- /dev/null +++ b/modules/material/dictionaries/error.definition.json @@ -0,0 +1,21 @@ + +{ + "title": { + "en": "Error", + "es": "Error", + "fr": "Erreur", + "ko": "오류" + }, + "header": { + "en": "Error", + "es": "Error", + "fr": "Erreur", + "ko": "오류" + }, + "message": { + "en": "An error occurred, please contact your help desk for further assistance.", + "es": "Se ha producido un error, póngase en contacto con su asistencia técnica para obtener más ayuda.", + "fr": "Une erreur s'est produite, s'il vous plaît contacter votre service d'assistance pour plus d'assistance.", + "ko": "오류가 발생했습니다. 도움을 받으려면 헬프 데스크에 문의하십시오." + } +} diff --git a/modules/material/dictionaries/expired.definition.json b/modules/material/dictionaries/expired.definition.json new file mode 100644 index 00000000..0915a459 --- /dev/null +++ b/modules/material/dictionaries/expired.definition.json @@ -0,0 +1,27 @@ + +{ + "title": { + "en": "Expired password", + "es": "Contraseña caducada", + "fr": "Mot de passe expiré", + "ko": "만료 된 암호" + }, + "header": { + "en": "Password expired", + "es": "La contraseña expiró", + "fr": "Mot de passe expiré", + "ko": "암호가 만료되었습니다." + }, + "expired": { + "en": "Your password has expired and must be changed before continuing.", + "es": "Su contraseña ha caducado y debe cambiarse antes de continuar.", + "fr": "Votre mot de passe a expiré et doit être modifié avant de continuer.", + "ko": "비밀번호가 만료되었으므로 계속하기 전에 비밀번호를 변경해야합니다." + }, + "button_change": { + "en": "Change", + "es": "Cambiar", + "fr": "Changer", + "ko": "바꾸다" + } +} diff --git a/modules/material/dictionaries/footer.definition.json b/modules/material/dictionaries/footer.definition.json new file mode 100644 index 00000000..0ee65023 --- /dev/null +++ b/modules/material/dictionaries/footer.definition.json @@ -0,0 +1,9 @@ + +{ + "copyright": { + "en": "Unauthorized use of this site is prohibited and may be subjected to civil and criminal prosecution.", + "es": "El uso no autorizado de este sitio está prohibido y puede ser sometido a procesamiento civil y penal.", + "fr": "L'utilisation non autorisée de ce site est interdite et peut faire l'objet de poursuites civiles et pénales.", + "ko": "이 사이트의 무단 사용은 금지되어 있으며 민사 및 형사 고발의 대상이 될 수 있습니다." + } +} diff --git a/modules/material/dictionaries/login.definition.json b/modules/material/dictionaries/login.definition.json new file mode 100644 index 00000000..e5d1d69b --- /dev/null +++ b/modules/material/dictionaries/login.definition.json @@ -0,0 +1,62 @@ +{ + "title": { + "en": "Login with your {idpName} identity", + "es": "Inicie sesión con su identidad de {idpName}", + "fr": "Connectez-vous avec votre identité {idpName}", + "ko": "{idpName} 신원 계정으로 로그인하십시오." + }, + "header": { + "en": "Login with your {idpName} identity", + "es": "Inicie sesión con su identidad de {idpName}", + "fr": "Connectez-vous avec votre identité {idpName}", + "ko": "{idpName} 신원 계정으로 로그인하십시오." + }, + "label_username": { + "en": "Username", + "es": "Nombre de usuario", + "fr": "Nom d'utilisateur", + "ko": "사용자 이름" + }, + "label_password": { + "en": "Password", + "es": "Contraseña", + "fr": "Mot de passe", + "ko": "암호" + }, + "error_wronguserpass": { + "en": "Something is wrong with that username or password, please verify and try again.", + "es": "Algo está mal con ese nombre de usuario o contraseña, compruebe e inténtelo de nuevo.", + "fr": "Quelque chose ne va pas avec ce nom d'utilisateur ou ce mot de passe, veuillez vérifier et essayer à nouveau.", + "ko": "해당 사용자 이름 또는 비밀번호가 잘못되었습니다. 다시 확인하고 다시 시도하십시오." + }, + "button_login": { + "en": "Login", + "es": "Iniciar sesión", + "fr": "Connexion", + "ko": "로그인" + }, + "forgot": { + "en": "Forgot password?", + "es": "¿Se te olvidó tu contraseña?", + "fr": "Mot de passe oublié?", + "ko": "비밀번호를 잊으 셨나요?" + }, + "logo": { + "en": "{idpName} logo", + "es": "Logotipo de {idpName}", + "fr": "Logo {idpName}", + "ko": "{idpName} 로고" + }, + "help": { + "en": "I need help", + "es": "necesito ayuda", + "fr": "j'ai besoin d'aide", + "ko": "도움이 필요해." + }, + "profile": { + "en": "Manage my profile", + "es": "Administrar mi perfil", + "fr": "Gérer mon profil", + "ko": "내 프로필 관리" + } +} diff --git a/modules/material/dictionaries/logout.definition.json b/modules/material/dictionaries/logout.definition.json new file mode 100644 index 00000000..61324e70 --- /dev/null +++ b/modules/material/dictionaries/logout.definition.json @@ -0,0 +1,21 @@ + +{ + "title": { + "en": "Logged out", + "es": "Desconectado", + "fr": "Déconnecté", + "ko": "로그 아웃 됨" + }, + "header": { + "en": "Logged out", + "es": "Desconectado", + "fr": "Déconnecté", + "ko": "로그 아웃 됨" + }, + "message": { + "en": "You have now been logged out.", + "es": "Se ha desconectado.", + "fr": "Vous êtes maintenant déconnecté.", + "ko": "이제 로그 아웃되었습니다." + } +} diff --git a/modules/material/dictionaries/mfa.definition.json b/modules/material/dictionaries/mfa.definition.json new file mode 100644 index 00000000..287648eb --- /dev/null +++ b/modules/material/dictionaries/mfa.definition.json @@ -0,0 +1,399 @@ + +{ + "title": { + "en": "2-Step Verification", + "es": "Verificación en 2 pasos", + "fr": "Vérification en deux étapes", + "ko": "2 단계 인증" + }, + "header": { + "en": "2-Step Verification", + "es": "Verificación en 2 pasos", + "fr": "Vérification en deux étapes", + "ko": "2 단계 인증" + }, + "backupcode_header": { + "en": "Printable code", + "es": "código imprimible", + "fr": "code imprimable", + "ko": "인쇄 가능한 코드" + }, + "backupcode_icon": { + "en": "Printable code icon", + "es": "icono de código imprimible", + "fr": "icône de code imprimable", + "ko": "인쇄 가능한 코드 아이콘" + }, + "backupcode_reminder": { + "en": "Each code can only be used once, so the code you enter this time will be used up and will not be available again.", + "es": "Cada código solo se puede usar una vez, por lo que el código que ingrese esta vez se agotará y no estará disponible nuevamente.", + "fr": "Chaque code ne peut être utilisé qu'une seule fois, de sorte que le code que vous entrez cette fois sera épuisé et ne sera plus disponible.", + "ko": "각 코드는 한번만 사용할 수 있으므로 이번에 입력한 코드는 소멸되어 다시 사용할 수 없습니다." + }, + "backupcode_input": { + "en": "Enter code", + "es": "Introduzca el código", + "fr": "Entrer le code", + "ko": "코드 입력" + }, + "totp_header": { + "en": "Get a code from your authenticator app", + "es": "Obtenga un código de su aplicación de autenticación", + "fr": "Obtenez un code depuis votre application d'authentification", + "ko": "인증 앱에서 코드 받기" + }, + "totp_icon": { + "en": "Authenticator app icon", + "es": "Icono de aplicación de teléfono inteligente", + "fr": "Icône de l'application Smartphone", + "ko": "인증 응용 프로그램 아이콘" + }, + "totp_instructions": { + "en": "You will need to check your authenticator app for the current code.", + "es": "Deberá verificar la aplicación de autenticación para ver el código actual.", + "fr": "Vous devriez vérifier l'application d'authentification pour voir le code actuel.", + "ko": "인증 앱에서 현재 코드를 확인해야합니다." + }, + "totp_input": { + "en": "Enter 6-digit code", + "es": "Ingrese el código de 6 dígitos", + "fr": "Entrer le code à 6 chiffres", + "ko": "6 자리 코드 입력" + }, + "u2f_header": { + "en": "Security key", + "es": "Clave de seguridad", + "fr": "Clé de sécurité", + "ko": "보안키" + }, + "webauthn_header": { + "en": "Security key", + "es": "Clave de seguridad", + "fr": "Clé de sécurité", + "ko": "보안키" + }, + "u2f_icon": { + "en": "USB key icon", + "es": "Icono de la llave USB", + "fr": "Icône de clé USB", + "ko": "USB 키 아이콘" + }, + "webauthn_icon": { + "en": "USB key icon", + "es": "Icono de la llave USB", + "fr": "Icône de clé USB", + "ko": "USB 키 아이콘" + }, + "u2f_instructions": { + "en": "You may now insert your security key and press its button.", + "es": "Ahora puede insertar su clave de seguridad y presionar su botón.", + "fr": "Vous pouvez maintenant insérer votre clé de sécurité et appuyer sur le bouton.", + "ko": "이제 보안 키를 삽입하고 단추를 누를 수 있습니다." + }, + "webauthn_instructions": { + "en": "You may now insert your security key and press its button.", + "es": "Ahora puede insertar su clave de seguridad y presionar su botón.", + "fr": "Vous pouvez maintenant insérer votre clé de sécurité et appuyer sur le bouton.", + "ko": "이제 보안 키를 삽입하고 단추를 누를 수 있습니다." + }, + "u2f_unsupported": { + "en": "Unsupported in your current browser. Please consider a more secure browser like Google Chrome.", + "es": "No compatible en su navegador actual. Considere un navegador más seguro como Google Chrome.", + "fr": "Non compatible avec votre navigateur actuel. Veuillez considérer un navigateur plus sûr comme Google Chrome.", + "ko": "현재 브라우저에서 지원되지 않습니다. Chrome과 같은 보다 안전한 브라우저를 고려하십시오." + }, + "webauthn_unsupported": { + "en": "Unsupported in your current browser. Please consider a more secure browser like Google Chrome.", + "es": "No compatible en su navegador actual. Considere un navegador más seguro como Google Chrome.", + "fr": "Non compatible avec votre navigateur actuel. Veuillez considérer un navigateur plus sûr comme Google Chrome.", + "ko": "현재 브라우저에서 지원되지 않습니다. Chrome과 같은 보다 안전한 브라우저를 고려하십시오." + }, + "u2f_error_unknown": { + "en": "Something went wrong with that request, unable to verify at this time.", + "es": "Algo salió mal con esa solicitud, no se pudo verificar en este momento.", + "fr": "Quelque chose s'est mal passé avec cette demande, impossible de vérifier pour le moment.", + "ko": "요청에 문제가 발생하여 지금은 확인할 수 없습니다." + }, + "webauthn_error_unknown": { + "en": "Something went wrong with that request, unable to verify at this time.", + "es": "Algo salió mal con esa solicitud, no se pudo verificar en este momento.", + "fr": "Quelque chose s'est mal passé avec cette demande, impossible de vérifier pour le moment.", + "ko": "요청에 문제가 발생하여 지금은 확인할 수 없습니다." + }, + "u2f_error_wrong_key": { + "en": "This may not be the correct key for this site.", + "es": "Esta puede no ser la clave correcta para este sitio.", + "fr": "Ce n'est peut-être pas la bonne clé pour ce site.", + "ko": "이 사이트의 올바른 키가 아닐 수도 있습니다." + }, + "u2f_error_timeout": { + "en": "That took a little too long, check to make sure your key is inserted right-side up.", + "es": "Eso llevó demasiado tiempo, verifique que su clave esté insertada boca arriba.", + "fr": "Cela a pris un peu trop de temps, vérifiez que votre clé est insérée dans le bons sens.", + "ko": "오랜 시간이 경과 되었으니 키가 오른쪽 위로 삽입되었는지 확인 하십시오." + }, + "webauthn_error_abort": { + "en": "It looks like you clicked cancel. Would you like us to try again?", + "es": "Parece que has hecho clic en cancelar. ¿Quieres que lo intentemos de nuevo?", + "fr": "Il semble que vous ayez cliqué sur annuler. Souhaitez-vous que nous essayions à nouveau ?", + "ko": "취소를 클릭하신 것 같습니다. 다시 시도해 보시겠어요?" + }, + "webauthn_error_not_allowed": { + "en": "Something about that didn't work. Please ensure that your security key is plugged in and that you touch it within 60 seconds when it blinks.", + "es": "Algo de eso no funcionó. Por favor, asegúrese de que su clave de seguridad está conectada y de que la toca en un plazo de 60 segundos cuando parpadea.", + "fr": "Quelque chose n'a pas fonctionné avec ça. Veuillez vous assurer que votre clé de sécurité est insérée et que vous la touchez dans les 60 secondes lorsqu'elle clignote.", + "ko": "문제가 해결되지 않았습니다. 보안 키가 연결되어 있고 깜박일 때 60초 이내에 터치했는지 확인하세요." + }, + "manager_icon": { + "en": "Recovery contact icon", + "es": "Icono de contacto de recuperación", + "fr": "Icône du contact de récupération", + "ko": "복구 연락처 아이콘" + }, + "manager_header": { + "en": "Ask Your Recovery Contact for Help", + "es": "Pida ayuda de contacto de recuperación", + "fr": "Demandez de l'aide à votre contact de récupération", + "ko": "복구 담당자에게 도움을 요청하십시오" + }, + "manager_info": { + "en": "You can send a 2-step verification code to your recovery contact (usually your supervisor). The email we have for your recovery contact is:

{managerEmail}

We've hidden most of the letters for your contact's protection.", + "es": "Puede enviar un código de verificación de dos pasos a su contacto de recuperación (normalmente su supervisor). La dirección de correo electrónico que tenemos para su contacto de recuperación es:

{managerEmail}

Ocultamos la mayoría de las letras para proteger a su contacto.", + "fr": "Vous pouvez envoyer un code de vérification en deux étapes à votre contact de récupération (en général votre superviseur). L'adresse électronique que nous avons pour votre contact de récupération est:

{managerEmail}

. Nous avons caché la plupart des lettres pour la protection de votre contact.", + "ko": "2단계 인증 코드를 복구 연락처(보통 상사)에게 보낼 수 있습니다. 복구 연락처에 대한 이메일은 다음과 같습니다.

{managerEmail}

연락처 보호를 위해 대부분의 편지를 숨겼습니다." + }, + "manager_sent": { + "en": "A temporary code was sent your recovery contact at {managerEmail}.", + "es": "Se envió un código temporal a su contacto de recuperación en {managerEmail}.", + "fr": "Un code temporaire a été envoyé à votre contact de récupération à l'adresse {managerEmail}.", + "ko": "{managerEmail} (으)로 복구 담당자에게 임시 코드를 보냈습니다." + }, + "manager_input": { + "en": "Enter code", + "es": "Introduzca el código", + "fr": "Entrer le code", + "ko": "코드 입력" + }, + "shield_icon": { + "en": "Shield icon", + "es": "Icono de escudo", + "fr": "Icône de bouclier", + "ko": "방패 아이콘" + }, + "required_header": { + "en": "Protect this account", + "es": "Protege esta cuenta", + "fr": "Protéger ce compte", + "ko": "이 계정 보호" + }, + "required_info": { + "en": "Your identity account requires additional security, you must set up 2-Step Verification at this time.", + "es": "Su cuenta de identidad requiere seguridad adicional, debe configurar la verificación en dos pasos en este momento.", + "fr": "Votre compte d'identité nécessite une sécurité supplémentaire, vous devez configurer la vérification en deux étapes en ce moment.", + "ko": "신원 계정에 추가 보안이 필요하므로 현재 2 단계 인증을 설정해야합니다." + }, + "running_out_header": { + "en": "Almost out of printable codes", + "es": "Casi sin códigos imprimibles", + "fr": "Codes imprimables presque épuisés", + "ko": "인쇄 ​가능한 ​코드​가 거의 남지 않았습니다" + }, + "running_out_info": { + "en": "You only have {numBackupCodesRemaining} more left.", + "es": "Solo tiene {numBackupCodesRemaining} más disponible.", + "fr": "Vous avez seulement {numBackupCodesRemaining} qui restent.", + "ko": "{numBackupCodesRemaining} 만 남았습니다." + }, + "no_more_codes_header": { + "en": "Last printable code used", + "es": "Último código imprimible utilizado", + "fr": "Dernier code imprimable utilisé", + "ko": "​​마지막 인쇄 가능 코드가​​ ​사용되었습니다." + }, + "new_codes_header": { + "en": "New printable codes", + "es": "Nuevos códigos imprimibles", + "fr": "Nouveaux codes imprimables", + "ko": "​​새로운 인쇄 가능 코드" + }, + "old_codes_gone": { + "en": "You may now discard any of your previous codes, they have been deleted.", + "es": "Ahora puede descartar cualquiera de sus códigos anteriores, se han eliminado.", + "fr": "Vous pouvez maintenant jeter tous vos codes précédents, ils ont été supprimés.", + "ko": "​​이제 이전 코드를 삭제해도 삭제 될 수 있습니다." + }, + "new_codes_info": { + "en": "Printable codes should be treated with the same level of attention as any password.", + "es": "Los códigos imprimibles deben tratarse con el mismo nivel de atención que cualquier contraseña.", + "fr": "Les codes imprimables doivent être traités avec le même niveau d'attention que les mots de passe.", + "ko": "​​인쇄 가능한 코드는 모든 비밀번호와 동일한주의 수준으로 처리되어야합니다." + }, + "new_codes_only_once": { + "en": "Each code may only be used once.", + "es": "Cada código solo puede usarse una vez.", + "fr": "Chaque code ne peut être utilisé qu'une seule fois.", + "ko": "​​각 코드는 한 번만 사용할 수 있습니다." + }, + "new_codes_failed": { + "en": "Something went wrong while creating new printable codes for you. We are sorry for the inconvenience, please check your configuration at the following address after continuing: ", + "es": "Algo salió mal al crear nuevos códigos imprimibles para usted. Disculpe las molestias, compruebe su configuración en la siguiente dirección después de continuar: ", + "fr": "Une erreur s'est produite lors de la création de nouveaux codes imprimables. Nous sommes désolés pour le dérangement. Veuillez vérifier votre configuration à l'adresse suivante après avoir continué: ", + "ko": "새로운 인쇄 가능한 코드를 만드는 동안 문제가 발생했습니다. 불편을 끼쳐 드려 죄송합니다. 계속 진행 한 후 다음 주소로 구성을 확인하십시오. " + }, + "new_codes_saved": { + "en": "I saved a personal copy of these for later use", + "es": "Guardé una copia personal de estos para uso posterior", + "fr": "J'ai sauvegardé une copie personnelle de ceux-ci pour une utilisation ultérieure", + "ko": "나는 나중에 사용하기 위해 이들의 개인 사본을 저장했다." + }, + "account": { + "en": "{idpName} identity account", + "es": "Cuenta de identidad de {idpName}", + "fr": "Compte d'identité {idpName}", + "ko": "{idpName} 신원 계정" + }, + "has_options_besides_codes": { + "en": "Thankfully you do have other 2-Step Verification options set up but you should create more Printable codes if you plan to need them in the future.", + "es": "Afortunadamente, tiene otras opciones de verificación en dos pasos configuradas, pero debe crear más códigos imprimibles si planea necesitarlos en el futuro.", + "fr": "Heureusement, vous avez d'autres options de vérification en deux étapes, mais vous devriez créer plus de codes imprimables si vous prévoyez en avoir besoin à l'avenir.", + "ko": "다른 2 단계 인증 옵션​은​ 설정​ 되었으나​ ​인쇄 가능 코드가 나중에 ​필요할 ​것으로 ​예상되면​ 코드를 ​더 ​만들어야​ ​합니다." + }, + "has_no_more_options": { + "en": "Since you do not have any other 2-Step Verification options set up at this time, you will need to get more Printable codes before another one is required.", + "es": "Como no tiene configuradas otras opciones de verificación en dos pasos en este momento, necesitará obtener más códigos imprimibles antes de que se requiera otro.", + "fr": "Comme aucune autre option de vérification en deux étapes n'est configurée pour l'instant, vous devez obtenir davantage de codes imprimables avant d'en avoir besoin d'un autre.", + "ko": "코드​가 요구되기 전에 인쇄 가능 코드를​ 더 가져와야​ ​합니다." + }, + "use_others": { + "en": "More options", + "es": "Mas opciones", + "fr": "Davantage d'options", + "ko": "추가 옵션" + }, + "use_u2f": { + "en": "Use my security key instead", + "es": "Use mi clave de seguridad en su lugar", + "fr": "Utiliser plutôt ma clé de sécurité", + "ko": "내 보안키 사용" + }, + "use_webauthn": { + "en": "Use my security key instead", + "es": "Use mi clave de seguridad en su lugar", + "fr": "Utiliser plutôt ma clé de sécurité", + "ko": "내 보안키 사용" + }, + "use_totp": { + "en": "Use my authenticator app instead", + "es": "Use la aplicación autenticación en su lugar", + "fr": "Utiliser plutôt mon application d'authentification", + "ko": "내 인증 앱 사용" + }, + "use_backupcode": { + "en": "Use a printable code instead", + "es": "Use un código imprimible en su lugar", + "fr": "Utiliser plutôt un code imprimable", + "ko": "인쇄 가능한 코드 사용" + }, + "use_help": { + "en": "I need help", + "es": "necesito ayuda", + "fr": "j'ai besoin d'aide", + "ko": "도움이 필요해." + }, + "use_manager": { + "en": "Use code from my recovery contact", + "es": "Usar código de mi contacto de recuperación", + "fr": "Utiliser le code de mon contact de récupération", + "ko": "복구 담당자의 코드 사용" + }, + "button_verify": { + "en": "Verify", + "es": "Verificar", + "fr": "Vérifier", + "ko": "검증" + }, + "button_later": { + "en": "Remind me later", + "es": "Recuérdame más tarde", + "fr": "Rappelez-moi plus tard", + "ko": "추후 알림" + }, + "button_enable": { + "en": "Enable now", + "es": "Habilite ahora", + "fr": "Activer maintenant", + "ko": "지금 사용" + }, + "button_set_up": { + "en": "Set up now", + "es": "Configurar ahora", + "fr": "Configurer maintenant", + "ko": "지금 설정" + }, + "button_try_again": { + "en": "Try again", + "es": "Inténtalo de nuevo", + "fr": "Essayer de nouveau", + "ko": "다시 시도" + }, + "button_get_more": { + "en": "Get more", + "es": "Obtenga más", + "fr": "Avoir plus", + "ko": "더​ ​​가져오기" + }, + "button_continue": { + "en": "Continue", + "es": "Continuar", + "fr": "Continuer", + "ko": "계속하다" + }, + "button_print": { + "en": "Print", + "es": "Imprimir", + "fr": "Imprimer", + "ko": "인쇄" + }, + "button_download": { + "en": "Download", + "es": "Descargar", + "fr": "Télécharger", + "ko": "다운로드" + }, + "button_copy": { + "en": "Copy", + "es": "Copiar", + "fr": "Copier", + "ko": "사본" + }, + "button_send": { + "en": "Send code", + "es": "Enviar código", + "fr": "Envoyer code", + "ko": "코드 보내기" + }, + "button_cancel": { + "en": "Cancel", + "es": "Cancelar", + "fr": "Annuler", + "ko": "취소" + }, + "button_copied": { + "en": "Copied ✓", + "es": "Copiado ✓", + "fr": "Copié ✓", + "ko": "복사 됨 ✓" + }, + "remember_this": { + "en": "Remember this browser for 30 days", + "es": "Recuerde esta navegador por 30 días", + "fr": "Se rappeler de ce navigatuer pour 30 jours", + "ko": "이 브라우저를 30일간 기억" + }, + "unsupported": { + "en": "Not supported in this browser", + "es": "No compatible con este navegador", + "fr": "Non pris en charge dans ce navigateur", + "ko": "이 브라우저에서는 지원되지 않습니다." + } +} diff --git a/modules/material/dictionaries/nag.definition.json b/modules/material/dictionaries/nag.definition.json new file mode 100644 index 00000000..9c450e90 --- /dev/null +++ b/modules/material/dictionaries/nag.definition.json @@ -0,0 +1,75 @@ + +{ + "mfa_title": { + "en": "2-Step Verification", + "es": "Verificación en 2 pasos", + "fr": "Vérification en deux étapes", + "ko": "2 단계 인증" + }, + "mfa_header": { + "en": "2-Step Verification", + "es": "Verificación en 2 pasos", + "fr": "Vérification en deux étapes", + "ko": "2 단계 인증" + }, + "method_title": { + "en": "Password recovery methods", + "es": "Métodos de recuperación de contraseña", + "fr": "Méthodes de récupération de mot de passe", + "ko": "비밀번호 복구 방법" + }, + "method_header": { + "en": "Password recovery methods", + "es": "Métodos de recuperación de contraseña", + "fr": "Méthodes de récupération de mot de passe", + "ko": "비밀번호 복구 방법" + }, + "shield_icon": { + "en": "Shield icon", + "es": "Icono de escudo", + "fr": "Icône de bouclier", + "ko": "방패 아이콘" + }, + "header": { + "en": "Protect yourself", + "es": "Protéjase", + "fr": "Protégez-vous", + "ko": "자기 보호" + }, + "mfa_info": { + "en": "Did you know you could easily increase the security of your identity account by enabling 2-Step Verification?", + "es": "¿Sabía que podría aumentar fácilmente la seguridad de su cuenta de identidad al habilitar la verificación en dos pasos?", + "fr": "Savez-vous que vous pouvez facilement augmenter la sécurité de votre compte d'identité en activant la vérification en deux étapes?", + "ko": "2 단계 인증을 사용하여 신원 계정의 보안을 쉽게 높일 수 있다는 사실을 알고 계셨습니까?" + }, + "method_info": { + "en": "Do you forget your password sometimes? Did you know it is very easy to add an alternate email address for password recovery just in case?", + "es": "¿Olvidas tu contraseña a veces? ¿Sabía que es muy fácil agregar una dirección de correo electrónico alternativa para recuperar la contraseña por si acaso?", + "fr": "Avez-vous oublié votre mot de passe parfois? Saviez-vous qu'il est très facile d'ajouter une adresse électronique de remplacement pour la récupération du mot de passe au cas où?", + "ko": "가끔 암호를 잊어 버리십니까? 혹시라도 비밀번호 복구를 위해 보조 이메일 주소를 추가하는 것이 매우 쉽다는 것을 알고 계셨습니까?" + }, + "button_later": { + "en": "Remind me later", + "es": "Recuérdame más tarde", + "fr": "Rappelez-moi plus tard", + "ko": "추후 알림" + }, + "button_learn_more": { + "en": "Learn more", + "es": "Aprende más", + "fr": "Apprendre encore plus", + "ko": "더 알아보기" + }, + "button_enable": { + "en": "Enable now", + "es": "Habilite ahora", + "fr": "Activer maintenant", + "ko": "지금 사용" + }, + "button_add": { + "en": "Add one now", + "es": "Agrega uno ahora", + "fr": "Ajouter un maintenant", + "ko": "지금 하나 추가" + } +} diff --git a/modules/material/dictionaries/review.definition.json b/modules/material/dictionaries/review.definition.json new file mode 100644 index 00000000..96764c14 --- /dev/null +++ b/modules/material/dictionaries/review.definition.json @@ -0,0 +1,75 @@ + +{ + "title": { + "en": "Profile review", + "es": "Revisión del perfil", + "fr": "Examen du profil", + "ko": "프로필 검토" + }, + "header": { + "en": "Profile review", + "es": "Revisión del perfil", + "fr": "Examen du profil", + "ko": "프로필 검토" + }, + "info": { + "en": "Are these still correct?", + "es": "¿Siguen siendo correctos?", + "fr": "Sont-ils toujours corrects?", + "ko": "여전히 맞습니까?" + }, + "mfa_header": { + "en": "2-Step Verification", + "es": "Verificación en 2 pasos", + "fr": "Vérification en 2 étapes", + "ko": "2 단계 인증" + }, + "methods_header": { + "en": "Password Recovery Methods", + "es": "Métodos de recuperación de contraseña", + "fr": "Méthodes de récupération de mot de passe", + "ko": "비밀번호 복구 방법" + }, + "remaining": { + "en": "({count} remaining)", + "es": "({count} restante)", + "fr": "({count} restant)", + "ko": "({count} 남음)" + }, + "used": { + "en": "last used: {when}", + "es": "último uso: {when}", + "fr": "dernière utilisation: {when}", + "ko": "마지막 사용 시간 : {when}" + }, + "used_never": { + "en": "last used: Never", + "es": "último uso: nunca", + "fr": "Dernière utilisation: Jamais", + "ko": "마지막 사용 : Never" + }, + "verified": { + "en": "Verified", + "es": "Verificado", + "fr": "Vérifié", + "ko": "검증 된" + }, + "unverified": { + "en": "Unverified", + "es": "Inconfirmado", + "fr": "Non vérifié", + "ko": "확인되지 않음" + }, + "button_update": { + "en": "Some of these need updating", + "es": "Algunos de estos necesitan actualización", + "fr": "Certains ont besoin d'être mis à jour", + "ko": "이들 중 일부는 업데이트해야합니다." + }, + "button_continue": { + "en": "These are still correct", + "es": "Estos siguen siendo correctos", + "fr": "Ceux-ci sont toujours corrects", + "ko": "이들은 여전히 정확하다." + } +} diff --git a/modules/material/dictionaries/selectidp.definition.json b/modules/material/dictionaries/selectidp.definition.json new file mode 100644 index 00000000..55a261ac --- /dev/null +++ b/modules/material/dictionaries/selectidp.definition.json @@ -0,0 +1,38 @@ +{ + "title": { + "en": "Choose an identity account", + "es": "Elige una cuenta de identidad", + "fr": "Choisissez un compte d'identité", + "ko": "ID 계정 선택" + }, + "header": { + "en": "Choose an identity account", + "es": "Elige una cuenta de identidad", + "fr": "Choisissez un compte d'identité", + "ko": "ID 계정 선택" + }, + "header-for-sp": { + "en": "Choose an identity account to continue to {spName}", + "es": "Elija una cuenta de identidad para continuar en {spName}", + "fr": "Choisissez un compte d'identité pour continuer vers {spName}", + "ko": "{spName}을 계속 진행하려면 신원 계정을 선택하십시오." + }, + "enabled": { + "en": "Login with your {idpName} identity account", + "es": "Inicie sesión con su cuenta de identidad {idpName}", + "fr": "Connectez-vous avec votre compte d'identité {idpName}", + "ko": "{idpName} 신원 계정으로 로그인하십시오." + }, + "disabled": { + "en": "{idpName} coming soon", + "es": "{IdpName} próximamente", + "fr": "{IdpName} à venir", + "ko": "{idpName} 곧 제공됨" + }, + "help": { + "en": "Help", + "es": "Ayuda", + "fr": "Aidez-moi", + "ko": "도움" + } +} diff --git a/modules/material/themes/material/common-announcement.php b/modules/material/themes/material/common-announcement.php new file mode 100644 index 00000000..74d5649b --- /dev/null +++ b/modules/material/themes/material/common-announcement.php @@ -0,0 +1,15 @@ + +
+ +
+ diff --git a/modules/material/themes/material/common-footer.php b/modules/material/themes/material/common-footer.php new file mode 100644 index 00000000..90fb7756 --- /dev/null +++ b/modules/material/themes/material/common-footer.php @@ -0,0 +1,3 @@ +
+ t('{material:footer:copyright}') ?> +
diff --git a/modules/material/themes/material/common-head-elements.php b/modules/material/themes/material/common-head-elements.php new file mode 100644 index 00000000..ceb5690d --- /dev/null +++ b/modules/material/themes/material/common-head-elements.php @@ -0,0 +1,33 @@ + + + + + + + +configuration->getValue('analytics.trackingId')); +if (! empty($trackingId)) { + ?> + + + + + +configuration->getValue('theme.color-scheme') ?: 'indigo-purple'); +?> + + + + + + diff --git a/modules/material/themes/material/core/loginuserpass.php b/modules/material/themes/material/core/loginuserpass.php new file mode 100644 index 00000000..dd950c80 --- /dev/null +++ b/modules/material/themes/material/core/loginuserpass.php @@ -0,0 +1,159 @@ + + + + configuration->getValue( + 'idp_display_name', + $this->configuration->getValue('idp_name', '—') + )); + ?> + + <?= $this->t('{material:login:title}', ['{idpName}' => $idpName]) ?> + + + + + + data['recaptcha.siteKey'] ?? null); + + if (! empty($siteKey)) { + ?> + + + + + + +
+
+ + +
+ + + data)) { + $csrfToken = htmlentities($this->data['csrfToken']); + ?> + + + +
+
+ <?= $this->t('{material:login:logo}', ['{idpName}' => $idpName]) ?> +
+ +
+

+ t('{material:login:header}', ['{idpName}' => $idpName]) ?> +

+
+ +
+
+ + + data['username'] ?? null); + ?> + id="username"/> +
+ +
+ + + + id="password"/> +
+
+ + data['errorcode'] ?? null; + if ($errorCode == 'WRONGUSERPASS') { + $errorMessageKey = $this->data['errorparams'][1] ?? '{material:login:error_wronguserpass}'; + $errorMessageTokens = $this->data['errorparams'][2] ?? null; + + $message = $this->t($errorMessageKey, $errorMessageTokens); + ?> +

+ error + + + + +

+ + + + +
+ configuration->getValue('passwordForgotUrl')); + if (! empty($forgotPasswordUrl)) { + ?> + + t('{material:login:forgot}') ?> + + + + + + +
+
+ +
+ data['helpCenterUrl'])): ?> + + t('{material:login:help}') ?> launch + + + + data['profileUrl'])): ?> + + t('{material:login:profile}') ?> launch + + +
+
+
+
+ + diff --git a/modules/material/themes/material/default/error.php b/modules/material/themes/material/default/error.php new file mode 100644 index 00000000..859b3fbf --- /dev/null +++ b/modules/material/themes/material/default/error.php @@ -0,0 +1,41 @@ + + + + <?= $this->t('{material:error:title}') ?> + + + + +
+
+
+ + t('{material:error:header}') ?> + +
+
+ +
+

+ t('{material:error:message}') ?> +

+ + data['showerrors'] ?? false) { + ?> +

+ data['error']['exceptionMsg']) ?> +

+ +
+            data['error']['exceptionTrace']) ?>
+        
+ +
+ + +
+ + diff --git a/modules/material/themes/material/default/logout.php b/modules/material/themes/material/default/logout.php new file mode 100644 index 00000000..c1d81d34 --- /dev/null +++ b/modules/material/themes/material/default/logout.php @@ -0,0 +1,27 @@ + + + + <?= $this->t('{material:logout:title}') ?> + + + + +
+
+
+ + t('{material:logout:header}') ?> + +
+
+ +
+

+ t('{material:logout:message}') ?> +

+
+ + +
+ + diff --git a/modules/material/themes/material/default/selectidp-links.php b/modules/material/themes/material/default/selectidp-links.php new file mode 100644 index 00000000..44a3d00b --- /dev/null +++ b/modules/material/themes/material/default/selectidp-links.php @@ -0,0 +1,168 @@ + + + + <?= $this->t('{material:selectidp:title}') ?> + + + + + + + +
+
+
+ + data['spName'] ?? null; + if (empty($spName)) { + echo $this->t('{material:selectidp:header}'); + } else { + echo htmlentities($this->t('{material:selectidp:header-for-sp}', ['{spName}' => $spName])); + } + ?> + + +
+ + data['helpCenterUrl'])): ?> + + +
+
+ +
+ + +
+ + + + + data['idplist']['dummy']); + + $enabledIdps = []; + $disabledIdps = []; + foreach ($this->data['idplist'] as $idp) { + $idp['enabled'] === true ? $enabledIdps[] = $idp + : $disabledIdps[] = $idp; + } + + foreach ($enabledIdps as $idp) { + $name = htmlentities($this->t($idp['name'])); + $idpId = htmlentities($idp['entityid']); + $hoverText = $this->t('{material:selectidp:enabled}', ['{idpName}' => $name]); + ?> +
+
+ +
+
+ + + t($idp['name'])); + $idpId = htmlentities($idp['entityid']); + $hoverText = $this->t('{material:selectidp:disabled}', ['{idpName}' => $name]); + ?> +
+
+
+
+ +
+ ': htmlentities($idp['logoCaption']) ?> +
+
+
+ +
+
+ + + +
+ + + diff --git a/modules/material/themes/material/expirychecker/about2expire.php b/modules/material/themes/material/expirychecker/about2expire.php new file mode 100644 index 00000000..6ef67783 --- /dev/null +++ b/modules/material/themes/material/expirychecker/about2expire.php @@ -0,0 +1,58 @@ + + + + <?= $this->t('{material:about2expire:title}') ?> + + + + +
+
+
+ + t('{material:about2expire:header}') ?> + +
+
+
+
+ data['formData'] as $name => $value) { + ?> + + + +

+ data['daysLeft'] ?? 0; + $expiringMessage = $daysLeft < 2 ? + $this->t('{material:about2expire:expiring_in_a_day}') : + $this->t('{material:about2expire:expiring_soon}', + ['{daysLeft}' => (string)$daysLeft]); + ?> + +

+ +

+ t('{material:about2expire:change_now}') ?> +

+ +
+ + + +
+
+
+ + +
+ + diff --git a/modules/material/themes/material/expirychecker/expired.php b/modules/material/themes/material/expirychecker/expired.php new file mode 100644 index 00000000..447fd685 --- /dev/null +++ b/modules/material/themes/material/expirychecker/expired.php @@ -0,0 +1,41 @@ + + + + <?= $this->t('{material:expired:title}') ?> + + + + +
+
+
+ + t('{material:expired:header}') ?> + +
+
+
+
+ data['formData'] as $name => $value) { + ?> + + + +

+ t('{material:expired:expired}') ?> +

+ + +
+
+ + +
+ + diff --git a/modules/material/themes/material/mfa/low-on-backup-codes.php b/modules/material/themes/material/mfa/low-on-backup-codes.php new file mode 100644 index 00000000..9f148a1c --- /dev/null +++ b/modules/material/themes/material/mfa/low-on-backup-codes.php @@ -0,0 +1,52 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+
+
+
+
+ warning +
+ +
+

+ t('{material:mfa:running_out_header}') ?> +

+
+ +
+

+ t('{material:mfa:running_out_info}', ['{numBackupCodesRemaining}' => (string)(int)$this->data['numBackupCodesRemaining']]) ?> +

+
+ +
+ + + + + +
+
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/must-set-up-mfa.php b/modules/material/themes/material/mfa/must-set-up-mfa.php new file mode 100644 index 00000000..8ae5c34f --- /dev/null +++ b/modules/material/themes/material/mfa/must-set-up-mfa.php @@ -0,0 +1,48 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+
+
+
+
+ <?= $this->t('{material:mfa:shield_icon}') ?> +
+ +
+

+ t('{material:mfa:required_header}') ?> +

+
+ +
+

+ t('{material:mfa:required_info}') ?> +

+
+ +
+ + + +
+
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/new-backup-codes.php b/modules/material/themes/material/mfa/new-backup-codes.php new file mode 100644 index 00000000..175f1548 --- /dev/null +++ b/modules/material/themes/material/mfa/new-backup-codes.php @@ -0,0 +1,158 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + + + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+
+
+ data['newBackupCodes']; ?> + +

+ t('{material:mfa:new_codes_header}') ?> +

+ +

+ t('{material:mfa:old_codes_gone}') ?> +

+ +

+ t('{material:mfa:new_codes_info}') ?> + t('{material:mfa:new_codes_only_once}') ?> +

+ +
+
+ configuration->getValue('idp_display_name', $this->configuration->getValue('idp_name', '—'))); + ?> +

+ t('{material:mfa:account}', ['{idpName}' => $idpName]) ?> + +

+ +
+ + + +
+ + t('{material:mfa:new_codes_only_once}') ?> +
+ +
+ + + + " + download="-printable-codes.txt" class="mdl-button mdl-button--primary"> + t('{material:mfa:button_download}') ?> + + + + +
+
+ +
+
+ error +
+ +
+

+ t('{material:error:header}') ?> +

+
+ +
+

+ t('{material:mfa:new_codes_failed}') ?> + data['mfaSetupUrl'] ?> +

+
+
+ + + + +
+ + + +
+ + +
+
+
+ + diff --git a/modules/material/themes/material/mfa/other_mfas.php b/modules/material/themes/material/mfa/other_mfas.php new file mode 100644 index 00000000..199336ca --- /dev/null +++ b/modules/material/themes/material/mfa/other_mfas.php @@ -0,0 +1,58 @@ +data['mfaOptions']; +$currentMfaId = filter_input(INPUT_GET, 'mfaId'); + +function excludeSelf($others, $selfId) { + return array_filter($others, function($option) use ($selfId) { + return $option['id'] != $selfId; + }); +} + +$otherOptions = excludeSelf($mfaOptions, $currentMfaId); + +if (! empty($this->data['managerEmail'])) { + $otherOptions[] = [ + 'type' => 'manager', + 'callback' => '/module.php/mfa/send-manager-mfa.php?StateId='.htmlentities($this->data['stateId']) + ]; +} + +if (count($otherOptions) > 0) { +?> +
+ + +
    + data['stateId']).'&mfaId='.htmlentities($option['id']); + + $image = 'mfa-' . $type . '.svg'; + $altText = $this->t('{material:mfa:' . $type . '_icon}'); + ?> +
  • + + <?= $altText ?> + + + t('{material:mfa:use_' . $label . '}') ?> + +
  • + +
+
+ diff --git a/modules/material/themes/material/mfa/out-of-backup-codes.php b/modules/material/themes/material/mfa/out-of-backup-codes.php new file mode 100644 index 00000000..cc4db6cc --- /dev/null +++ b/modules/material/themes/material/mfa/out-of-backup-codes.php @@ -0,0 +1,58 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+
+
+
+
+ error +
+ +
+

+ t('{material:mfa:no_more_codes_header}') ?> +

+
+ +
+

+ data['hasOtherMfaOptions']): ?> + t('{material:mfa:has_options_besides_codes}') ?> + + t('{material:mfa:has_no_more_options}') ?> + +

+
+ +
+ data['hasOtherMfaOptions']): ?> + + + + + + +
+
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.php b/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.php new file mode 100644 index 00000000..7520467a --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.php @@ -0,0 +1,92 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+
+
+
+
+ <?= $this->t('{material:mfa:backupcode_icon}') ?> +
+ +
+

+ t('{material:mfa:backupcode_header}') ?> +

+
+ +
+

+ t('{material:mfa:backupcode_reminder}') ?> +

+
+ +
+
+ + +
+
+ + data['errorMessage']; + + if (! empty($message)) { + ?> +
+

+ error + + + + +

+
+ + + + +
+ + +
+ + +
+ +
+ +
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-manager.php b/modules/material/themes/material/mfa/prompt-for-mfa-manager.php new file mode 100644 index 00000000..e9899947 --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-manager.php @@ -0,0 +1,89 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+
+
+
+
+ <?= $this->t('{material:mfa:manager_icon}') ?> +
+ +
+

+ t('{material:mfa:manager_header}') ?> +

+
+ +
+

+ t('{material:mfa:manager_sent}', ['{managerEmail}' => $this->data['managerEmail']]) ?> +

+
+ +
+
+ + +
+
+ + data['errorMessage']; + + if (! empty($message)) { + ?> +
+

+ error + + + + +

+
+ + + + +
+ + +
+ + +
+ +
+ +
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-totp.php b/modules/material/themes/material/mfa/prompt-for-mfa-totp.php new file mode 100644 index 00000000..b0d10248 --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-totp.php @@ -0,0 +1,93 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+
+
+
+
+ <?= $this->t('{material:mfa:totp_icon}') ?> +
+ +
+

+ t('{material:mfa:totp_header}') ?> +

+
+ +
+ configuration->getValue('idp_display_name', $this->configuration->getValue('idp_name', '—'))); + echo $this->t('{material:mfa:account}', ['{idpName}' => $idpName]); + ?> +
+ +
+
+ + +
+
+ + data['errorMessage']; + + if (! empty($message)) { + ?> +
+

+ error + + + + +

+
+ + + + +
+ + +
+ + +
+ +
+ +
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-u2f.php b/modules/material/themes/material/mfa/prompt-for-mfa-u2f.php new file mode 100644 index 00000000..bc867991 --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-u2f.php @@ -0,0 +1,175 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + + + + + +data['supportsU2f']; ?> + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+ +
+
+
+
+ <?= $this->t('{material:mfa:u2f_icon}') ?> +
+ +
+

+ t('{material:mfa:u2f_header}') ?> +

+
+ + +
+

+ t('{material:mfa:u2f_instructions}') ?> +

+
+ +
+

+ t('{material:mfa:u2f_unsupported}') ?> +

+
+ + + data['errorMessage']; + if (! empty($message)) { + ?> + + +
+

+ error + + + + +

+
+ +
+ + + + +
+ + +
+ +
+ +
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php b/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php new file mode 100644 index 00000000..7f8ccbe2 --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php @@ -0,0 +1,157 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + + + + + + +data['supportsWebAuthn']; ?> + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+ +
+
+
+
+ <?= $this->t('{material:mfa:webauthn_icon}') ?> +
+ +
+

+ t('{material:mfa:webauthn_header}') ?> +

+
+ + +
+

+ t('{material:mfa:webauthn_instructions}') ?> +

+
+ +
+

+ t('{material:mfa:webauthn_unsupported}') ?> +

+
+ + + data['errorMessage']; + if (! empty($message)) { + ?> + + +
+

+ error + + + + +

+
+ +
+ + + + +
+ + +
+ +
+ +
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/send-manager-mfa.php b/modules/material/themes/material/mfa/send-manager-mfa.php new file mode 100644 index 00000000..8bb30a8c --- /dev/null +++ b/modules/material/themes/material/mfa/send-manager-mfa.php @@ -0,0 +1,51 @@ + + + + <?= $this->t('{material:mfa:title}') ?> + + + + +
+
+
+ + t('{material:mfa:header}') ?> + +
+
+
+
+
+
+ <?= $this->t('{material:mfa:manager_icon}') ?> +
+ +
+

+ t('{material:mfa:manager_header}') ?> +

+
+ +
+

+ t('{material:mfa:manager_info}', ['{managerEmail}' => $this->data['managerEmail']]) ?> +

+
+ +
+ + + + +
+
+
+
+
+ + diff --git a/modules/material/themes/material/profilereview/nag-for-method.php b/modules/material/themes/material/profilereview/nag-for-method.php new file mode 100644 index 00000000..7e6d50d1 --- /dev/null +++ b/modules/material/themes/material/profilereview/nag-for-method.php @@ -0,0 +1,52 @@ + + + + <?= $this->t('{material:nag:method_title}') ?> + + + + +
+
+
+ + t('{material:nag:method_header}') ?> + +
+
+
+
+
+
+ <?= $this->t('{material:nag:shield_icon}') ?> +
+ +
+

+ t('{material:nag:header}') ?> +

+
+ +
+

+ t('{material:nag:method_info}') ?> +

+
+ +
+ + + + + +
+
+
+
+
+ + diff --git a/modules/material/themes/material/profilereview/nag-for-mfa.php b/modules/material/themes/material/profilereview/nag-for-mfa.php new file mode 100644 index 00000000..e18c9964 --- /dev/null +++ b/modules/material/themes/material/profilereview/nag-for-mfa.php @@ -0,0 +1,63 @@ + + + + <?= $this->t('{material:nag:mfa_title}') ?> + + + + +
+
+
+ + t('{material:nag:mfa_header}') ?> + +
+
+
+
+
+
+ <?= $this->t('{material:nag:shield_icon}') ?> +
+ +
+

+ t('{material:nag:header}') ?> +

+
+ +
+

+ t('{material:nag:mfa_info}') ?> +

+
+ +
+ + + + + data['mfaLearnMoreUrl'] ?? null; + if (! empty($url)) { + ?> + + t('{material:nag:button_learn_more}') ?> + + + + +
+
+
+
+
+ + diff --git a/modules/material/themes/material/profilereview/review.php b/modules/material/themes/material/profilereview/review.php new file mode 100644 index 00000000..039f4692 --- /dev/null +++ b/modules/material/themes/material/profilereview/review.php @@ -0,0 +1,128 @@ + + + + <?= $this->t('{material:review:title}') ?> + + + + + + +
+
+
+ + t('{material:review:header}') ?> + +
+
+ +
+
+

+

+ t('{material:review:info}') ?> +

+

+ +
+ data['mfaOptions']) > 0): ?> +
+
+

+ t('{material:review:mfa_header}') ?> +

+
+ +
+
    + data['mfaOptions'] as $mfa): ?> +
  • + + + + + + + t('{material:review:remaining}', ['{count}' => (string) $mfa['data']['count']]) ?> + + + + + t('{material:review:used_never}') ?> + + t('{material:review:used}', ['{when}' => $mfa['last_used_utc']]) ?> + + + +
  • + +
+
+
+ + + data['methodOptions']) > 0): ?> + +
+
+

+ t('{material:review:methods_header}') ?> +

+
+ +
+
    + data['methodOptions'] as $method): ?> +
  • + + + + + + + t('{material:review:'.($method['verified'] ? 'verified' : 'unverified').'}') ?> + + +
  • + +
+
+
+ +
+ +
+ + t('{material:review:button_update}') ?> launch + + + +
+
+
+ + +
+ + diff --git a/modules/material/www/bowser.1.9.4.min.js b/modules/material/www/bowser.1.9.4.min.js new file mode 100644 index 00000000..5c5d3733 --- /dev/null +++ b/modules/material/www/bowser.1.9.4.min.js @@ -0,0 +1,6 @@ +/*! + * Bowser - a browser detector + * https://github.com/ded/bowser + * MIT License | (c) Dustin Diaz 2015 + */ +!function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(t,n):e[t]=n()}(this,"bowser",function(){function t(t){function n(e){var n=t.match(e);return n&&n.length>1&&n[1]||""}function r(e){var n=t.match(e);return n&&n.length>1&&n[2]||""}function C(e){switch(e){case"NT":return"NT";case"XP":return"XP";case"NT 5.0":return"2000";case"NT 5.1":return"XP";case"NT 5.2":return"2003";case"NT 6.0":return"Vista";case"NT 6.1":return"7";case"NT 6.2":return"8";case"NT 6.3":return"8.1";case"NT 10.0":return"10";default:return undefined}}var i=n(/(ipod|iphone|ipad)/i).toLowerCase(),o=/like android/i.test(t),u=!o&&/android/i.test(t),a=/nexus\s*[0-6]\s*/i.test(t),f=!a&&/nexus\s*[0-9]+/i.test(t),l=/CrOS/.test(t),c=/silk/i.test(t),h=/sailfish/i.test(t),p=/tizen/i.test(t),d=/(web|hpw)(o|0)s/i.test(t),v=/windows phone/i.test(t),m=/SamsungBrowser/i.test(t),g=!v&&/windows/i.test(t),y=!i&&!c&&/macintosh/i.test(t),b=!u&&!h&&!p&&!d&&/linux/i.test(t),w=r(/edg([ea]|ios)\/(\d+(\.\d+)?)/i),E=n(/version\/(\d+(\.\d+)?)/i),S=/tablet/i.test(t)&&!/tablet pc/i.test(t),x=!S&&/[^-]mobi/i.test(t),T=/xbox/i.test(t),N;/opera/i.test(t)?N={name:"Opera",opera:e,version:E||n(/(?:opera|opr|opios)[\s\/](\d+(\.\d+)?)/i)}:/opr\/|opios/i.test(t)?N={name:"Opera",opera:e,version:n(/(?:opr|opios)[\s\/](\d+(\.\d+)?)/i)||E}:/SamsungBrowser/i.test(t)?N={name:"Samsung Internet for Android",samsungBrowser:e,version:E||n(/(?:SamsungBrowser)[\s\/](\d+(\.\d+)?)/i)}:/Whale/i.test(t)?N={name:"NAVER Whale browser",whale:e,version:n(/(?:whale)[\s\/](\d+(?:\.\d+)+)/i)}:/MZBrowser/i.test(t)?N={name:"MZ Browser",mzbrowser:e,version:n(/(?:MZBrowser)[\s\/](\d+(?:\.\d+)+)/i)}:/coast/i.test(t)?N={name:"Opera Coast",coast:e,version:E||n(/(?:coast)[\s\/](\d+(\.\d+)?)/i)}:/focus/i.test(t)?N={name:"Focus",focus:e,version:n(/(?:focus)[\s\/](\d+(?:\.\d+)+)/i)}:/yabrowser/i.test(t)?N={name:"Yandex Browser",yandexbrowser:e,version:E||n(/(?:yabrowser)[\s\/](\d+(\.\d+)?)/i)}:/ucbrowser/i.test(t)?N={name:"UC Browser",ucbrowser:e,version:n(/(?:ucbrowser)[\s\/](\d+(?:\.\d+)+)/i)}:/mxios/i.test(t)?N={name:"Maxthon",maxthon:e,version:n(/(?:mxios)[\s\/](\d+(?:\.\d+)+)/i)}:/epiphany/i.test(t)?N={name:"Epiphany",epiphany:e,version:n(/(?:epiphany)[\s\/](\d+(?:\.\d+)+)/i)}:/puffin/i.test(t)?N={name:"Puffin",puffin:e,version:n(/(?:puffin)[\s\/](\d+(?:\.\d+)?)/i)}:/sleipnir/i.test(t)?N={name:"Sleipnir",sleipnir:e,version:n(/(?:sleipnir)[\s\/](\d+(?:\.\d+)+)/i)}:/k-meleon/i.test(t)?N={name:"K-Meleon",kMeleon:e,version:n(/(?:k-meleon)[\s\/](\d+(?:\.\d+)+)/i)}:v?(N={name:"Windows Phone",osname:"Windows Phone",windowsphone:e},w?(N.msedge=e,N.version=w):(N.msie=e,N.version=n(/iemobile\/(\d+(\.\d+)?)/i))):/msie|trident/i.test(t)?N={name:"Internet Explorer",msie:e,version:n(/(?:msie |rv:)(\d+(\.\d+)?)/i)}:l?N={name:"Chrome",osname:"Chrome OS",chromeos:e,chromeBook:e,chrome:e,version:n(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)}:/edg([ea]|ios)/i.test(t)?N={name:"Microsoft Edge",msedge:e,version:w}:/vivaldi/i.test(t)?N={name:"Vivaldi",vivaldi:e,version:n(/vivaldi\/(\d+(\.\d+)?)/i)||E}:h?N={name:"Sailfish",osname:"Sailfish OS",sailfish:e,version:n(/sailfish\s?browser\/(\d+(\.\d+)?)/i)}:/seamonkey\//i.test(t)?N={name:"SeaMonkey",seamonkey:e,version:n(/seamonkey\/(\d+(\.\d+)?)/i)}:/firefox|iceweasel|fxios/i.test(t)?(N={name:"Firefox",firefox:e,version:n(/(?:firefox|iceweasel|fxios)[ \/](\d+(\.\d+)?)/i)},/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(t)&&(N.firefoxos=e,N.osname="Firefox OS")):c?N={name:"Amazon Silk",silk:e,version:n(/silk\/(\d+(\.\d+)?)/i)}:/phantom/i.test(t)?N={name:"PhantomJS",phantom:e,version:n(/phantomjs\/(\d+(\.\d+)?)/i)}:/slimerjs/i.test(t)?N={name:"SlimerJS",slimer:e,version:n(/slimerjs\/(\d+(\.\d+)?)/i)}:/blackberry|\bbb\d+/i.test(t)||/rim\stablet/i.test(t)?N={name:"BlackBerry",osname:"BlackBerry OS",blackberry:e,version:E||n(/blackberry[\d]+\/(\d+(\.\d+)?)/i)}:d?(N={name:"WebOS",osname:"WebOS",webos:e,version:E||n(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i)},/touchpad\//i.test(t)&&(N.touchpad=e)):/bada/i.test(t)?N={name:"Bada",osname:"Bada",bada:e,version:n(/dolfin\/(\d+(\.\d+)?)/i)}:p?N={name:"Tizen",osname:"Tizen",tizen:e,version:n(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i)||E}:/qupzilla/i.test(t)?N={name:"QupZilla",qupzilla:e,version:n(/(?:qupzilla)[\s\/](\d+(?:\.\d+)+)/i)||E}:/chromium/i.test(t)?N={name:"Chromium",chromium:e,version:n(/(?:chromium)[\s\/](\d+(?:\.\d+)?)/i)||E}:/chrome|crios|crmo/i.test(t)?N={name:"Chrome",chrome:e,version:n(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)}:u?N={name:"Android",version:E}:/safari|applewebkit/i.test(t)?(N={name:"Safari",safari:e},E&&(N.version=E)):i?(N={name:i=="iphone"?"iPhone":i=="ipad"?"iPad":"iPod"},E&&(N.version=E)):/googlebot/i.test(t)?N={name:"Googlebot",googlebot:e,version:n(/googlebot\/(\d+(\.\d+))/i)||E}:N={name:n(/^(.*)\/(.*) /),version:r(/^(.*)\/(.*) /)},!N.msedge&&/(apple)?webkit/i.test(t)?(/(apple)?webkit\/537\.36/i.test(t)?(N.name=N.name||"Blink",N.blink=e):(N.name=N.name||"Webkit",N.webkit=e),!N.version&&E&&(N.version=E)):!N.opera&&/gecko\//i.test(t)&&(N.name=N.name||"Gecko",N.gecko=e,N.version=N.version||n(/gecko\/(\d+(\.\d+)?)/i)),!N.windowsphone&&(u||N.silk)?(N.android=e,N.osname="Android"):!N.windowsphone&&i?(N[i]=e,N.ios=e,N.osname="iOS"):y?(N.mac=e,N.osname="macOS"):T?(N.xbox=e,N.osname="Xbox"):g?(N.windows=e,N.osname="Windows"):b&&(N.linux=e,N.osname="Linux");var k="";N.windows?k=C(n(/Windows ((NT|XP)( \d\d?.\d)?)/i)):N.windowsphone?k=n(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i):N.mac?(k=n(/Mac OS X (\d+([_\.\s]\d+)*)/i),k=k.replace(/[_\s]/g,".")):i?(k=n(/os (\d+([_\s]\d+)*) like mac os x/i),k=k.replace(/[_\s]/g,".")):u?k=n(/android[ \/-](\d+(\.\d+)*)/i):N.webos?k=n(/(?:web|hpw)os\/(\d+(\.\d+)*)/i):N.blackberry?k=n(/rim\stablet\sos\s(\d+(\.\d+)*)/i):N.bada?k=n(/bada\/(\d+(\.\d+)*)/i):N.tizen&&(k=n(/tizen[\/\s](\d+(\.\d+)*)/i)),k&&(N.osversion=k);var L=!N.windows&&k.split(".")[0];if(S||f||i=="ipad"||u&&(L==3||L>=4&&!x)||N.silk)N.tablet=e;else if(x||i=="iphone"||i=="ipod"||u||a||N.blackberry||N.webos||N.bada)N.mobile=e;return N.msedge||N.msie&&N.version>=10||N.yandexbrowser&&N.version>=15||N.vivaldi&&N.version>=1||N.chrome&&N.version>=20||N.samsungBrowser&&N.version>=4||N.whale&&s([N.version,"1.0"])===1||N.mzbrowser&&s([N.version,"6.0"])===1||N.focus&&s([N.version,"1.0"])===1||N.firefox&&N.version>=20||N.safari&&N.version>=6||N.opera&&N.version>=10||N.ios&&N.osversion&&N.osversion.split(".")[0]>=6||N.blackberry&&N.version>=10.1||N.chromium&&N.version>=20?N.a=e:N.msie&&N.version<10||N.chrome&&N.version<20||N.firefox&&N.version<20||N.safari&&N.version<6||N.opera&&N.version<10||N.ios&&N.osversion&&N.osversion.split(".")[0]<6||N.chromium&&N.version<20?N.c=e:N.x=e,N}function r(e){return e.split(".").length}function i(e,t){var n=[],r;if(Array.prototype.map)return Array.prototype.map.call(e,t);for(r=0;r=0){if(n[0][t]>n[1][t])return 1;if(n[0][t]!==n[1][t])return-1;if(t===0)return 0}}function o(e,r,i){var o=n;typeof r=="string"&&(i=r,r=void 0),r===void 0&&(r=!1),i&&(o=t(i));var u=""+o.version;for(var a in e)if(e.hasOwnProperty(a)&&o[a]){if(typeof e[a]!="string")throw new Error("Browser version in the minVersion map should be a string: "+a+": "+String(e));return s([u,e[a]])<0}return r}function u(e,t,n){return!o(e,t,n)}var e=!0,n=t(typeof navigator!="undefined"?navigator.userAgent||"":"");return n.test=function(e){for(var t=0;t \ No newline at end of file diff --git a/modules/material/www/material-icons.woff b/modules/material/www/material-icons.woff new file mode 100644 index 00000000..e2cd4f1b Binary files /dev/null and b/modules/material/www/material-icons.woff differ diff --git a/modules/material/www/material-icons.woff2 b/modules/material/www/material-icons.woff2 new file mode 100644 index 00000000..dfad0dcf Binary files /dev/null and b/modules/material/www/material-icons.woff2 differ diff --git a/modules/material/www/material.1.2.1.min.js b/modules/material/www/material.1.2.1.min.js new file mode 100644 index 00000000..885e436a --- /dev/null +++ b/modules/material/www/material.1.2.1.min.js @@ -0,0 +1,9 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.2.1 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +!function(){"use strict";function e(e,t){if(e){if(t.element_.classList.contains(t.CssClasses_.MDL_JS_RIPPLE_EFFECT)){var s=document.createElement("span");s.classList.add(t.CssClasses_.MDL_RIPPLE_CONTAINER),s.classList.add(t.CssClasses_.MDL_JS_RIPPLE_EFFECT);var i=document.createElement("span");i.classList.add(t.CssClasses_.MDL_RIPPLE),s.appendChild(i),e.appendChild(s)}e.addEventListener("click",function(s){s.preventDefault();var i=e.href.split("#")[1],n=t.element_.querySelector("#"+i);t.resetTabState_(),t.resetPanelState_(),e.classList.add(t.CssClasses_.ACTIVE_CLASS),n.classList.add(t.CssClasses_.ACTIVE_CLASS)})}}function t(e,t,s,i){function n(){var n=e.href.split("#")[1],a=i.content_.querySelector("#"+n);i.resetTabState_(t),i.resetPanelState_(s),e.classList.add(i.CssClasses_.IS_ACTIVE),a.classList.add(i.CssClasses_.IS_ACTIVE)}if(i.tabBar_.classList.contains(i.CssClasses_.JS_RIPPLE_EFFECT)){var a=document.createElement("span");a.classList.add(i.CssClasses_.RIPPLE_CONTAINER),a.classList.add(i.CssClasses_.JS_RIPPLE_EFFECT);var l=document.createElement("span");l.classList.add(i.CssClasses_.RIPPLE),a.appendChild(l),e.appendChild(a)}e.addEventListener("click",function(t){"#"===e.getAttribute("href").charAt(0)&&(t.preventDefault(),n())}),e.show=n}var s={upgradeDom:function(e,t){},upgradeElement:function(e,t){},upgradeElements:function(e){},upgradeAllRegistered:function(){},registerUpgradedCallback:function(e,t){},register:function(e){},downgradeElements:function(e){}};s=function(){function e(e,t){for(var s=0;s0&&a(t.children))}function l(t){var s="undefined"==typeof t.widget&&"undefined"==typeof t.widget,i=!0;s||(i=t.widget||t.widget);var n={classConstructor:t.constructor||t.constructor,className:t.classAsString||t.classAsString,cssClass:t.cssClass||t.cssClass,widget:i,callbacks:[]};if(h.forEach(function(e){if(e.cssClass===n.cssClass)throw new Error("The provided cssClass has already been registered: "+e.cssClass);if(e.className===n.className)throw new Error("The provided className has already been registered")}),t.constructor.prototype.hasOwnProperty(p))throw new Error("MDL component classes must not have "+p+" defined as a property.");var a=e(t.classAsString,n);a||h.push(n)}function o(t,s){var i=e(t);i&&i.callbacks.push(s)}function r(){for(var e=0;e0&&this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)&&(e.keyCode===this.Keycodes_.UP_ARROW?(e.preventDefault(),t[t.length-1].focus()):e.keyCode===this.Keycodes_.DOWN_ARROW&&(e.preventDefault(),t[0].focus()))}},d.prototype.handleItemKeyboardEvent_=function(e){if(this.element_&&this.container_){var t=this.element_.querySelectorAll("."+this.CssClasses_.ITEM+":not([disabled])");if(t&&t.length>0&&this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)){var s=Array.prototype.slice.call(t).indexOf(e.target);if(e.keyCode===this.Keycodes_.UP_ARROW)e.preventDefault(),s>0?t[s-1].focus():t[t.length-1].focus();else if(e.keyCode===this.Keycodes_.DOWN_ARROW)e.preventDefault(),t.length>s+1?t[s+1].focus():t[0].focus();else if(e.keyCode===this.Keycodes_.SPACE||e.keyCode===this.Keycodes_.ENTER){e.preventDefault();var i=new MouseEvent("mousedown");e.target.dispatchEvent(i),i=new MouseEvent("mouseup"),e.target.dispatchEvent(i),e.target.click()}else e.keyCode===this.Keycodes_.ESCAPE&&(e.preventDefault(),this.hide())}}},d.prototype.handleItemClick_=function(e){e.target.hasAttribute("disabled")?e.stopPropagation():(this.closing_=!0,window.setTimeout(function(e){this.hide(),this.closing_=!1}.bind(this),this.Constant_.CLOSE_TIMEOUT))},d.prototype.applyClip_=function(e,t){this.element_.classList.contains(this.CssClasses_.UNALIGNED)?this.element_.style.clip="":this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)?this.element_.style.clip="rect(0 "+t+"px 0 "+t+"px)":this.element_.classList.contains(this.CssClasses_.TOP_LEFT)?this.element_.style.clip="rect("+e+"px 0 "+e+"px 0)":this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)?this.element_.style.clip="rect("+e+"px "+t+"px "+e+"px "+t+"px)":this.element_.style.clip=""},d.prototype.removeAnimationEndListener_=function(e){e.target.classList.remove(d.prototype.CssClasses_.IS_ANIMATING)},d.prototype.addAnimationEndListener_=function(){this.element_.addEventListener("transitionend",this.removeAnimationEndListener_),this.element_.addEventListener("webkitTransitionEnd",this.removeAnimationEndListener_)},d.prototype.show=function(e){if(this.element_&&this.container_&&this.outline_){var t=this.element_.getBoundingClientRect().height,s=this.element_.getBoundingClientRect().width;this.container_.style.width=s+"px",this.container_.style.height=t+"px",this.outline_.style.width=s+"px",this.outline_.style.height=t+"px";for(var i=this.Constant_.TRANSITION_DURATION_SECONDS*this.Constant_.TRANSITION_DURATION_FRACTION,n=this.element_.querySelectorAll("."+this.CssClasses_.ITEM),a=0;a0&&this.showSnackbar(this.queuedNotifications_.shift())},C.prototype.cleanup_=function(){this.element_.classList.remove(this.cssClasses_.ACTIVE),setTimeout(function(){this.element_.setAttribute("aria-hidden","true"),this.textElement_.textContent="",Boolean(this.actionElement_.getAttribute("aria-hidden"))||(this.setActionHidden_(!0),this.actionElement_.textContent="",this.actionElement_.removeEventListener("click",this.actionHandler_)),this.actionHandler_=void 0,this.message_=void 0,this.actionText_=void 0,this.active=!1,this.checkQueue_()}.bind(this),this.Constant_.ANIMATION_LENGTH)},C.prototype.setActionHidden_=function(e){e?this.actionElement_.setAttribute("aria-hidden","true"):this.actionElement_.removeAttribute("aria-hidden")},s.register({constructor:C,classAsString:"MaterialSnackbar",cssClass:"mdl-js-snackbar",widget:!0});var u=function(e){this.element_=e,this.init()};window.MaterialSpinner=u,u.prototype.Constant_={MDL_SPINNER_LAYER_COUNT:4},u.prototype.CssClasses_={MDL_SPINNER_LAYER:"mdl-spinner__layer",MDL_SPINNER_CIRCLE_CLIPPER:"mdl-spinner__circle-clipper",MDL_SPINNER_CIRCLE:"mdl-spinner__circle",MDL_SPINNER_GAP_PATCH:"mdl-spinner__gap-patch",MDL_SPINNER_LEFT:"mdl-spinner__left",MDL_SPINNER_RIGHT:"mdl-spinner__right"},u.prototype.createLayer=function(e){var t=document.createElement("div");t.classList.add(this.CssClasses_.MDL_SPINNER_LAYER),t.classList.add(this.CssClasses_.MDL_SPINNER_LAYER+"-"+e);var s=document.createElement("div");s.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER),s.classList.add(this.CssClasses_.MDL_SPINNER_LEFT);var i=document.createElement("div");i.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH);var n=document.createElement("div");n.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER),n.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT);for(var a=[s,i,n],l=0;l=this.maxRows&&e.preventDefault()},L.prototype.onFocus_=function(e){this.element_.classList.add(this.CssClasses_.IS_FOCUSED)},L.prototype.onBlur_=function(e){this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},L.prototype.onReset_=function(e){this.updateClasses_()},L.prototype.updateClasses_=function(){this.checkDisabled(),this.checkValidity(),this.checkDirty(),this.checkFocus()},L.prototype.checkDisabled=function(){this.input_.disabled?this.element_.classList.add(this.CssClasses_.IS_DISABLED):this.element_.classList.remove(this.CssClasses_.IS_DISABLED)},L.prototype.checkDisabled=L.prototype.checkDisabled,L.prototype.checkFocus=function(){Boolean(this.element_.querySelector(":focus"))?this.element_.classList.add(this.CssClasses_.IS_FOCUSED):this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},L.prototype.checkFocus=L.prototype.checkFocus,L.prototype.checkValidity=function(){this.input_.validity&&(this.input_.validity.valid?this.element_.classList.remove(this.CssClasses_.IS_INVALID):this.element_.classList.add(this.CssClasses_.IS_INVALID))},L.prototype.checkValidity=L.prototype.checkValidity,L.prototype.checkDirty=function(){this.input_.value&&this.input_.value.length>0?this.element_.classList.add(this.CssClasses_.IS_DIRTY):this.element_.classList.remove(this.CssClasses_.IS_DIRTY)},L.prototype.checkDirty=L.prototype.checkDirty,L.prototype.disable=function(){this.input_.disabled=!0,this.updateClasses_()},L.prototype.disable=L.prototype.disable,L.prototype.enable=function(){this.input_.disabled=!1,this.updateClasses_()},L.prototype.enable=L.prototype.enable,L.prototype.change=function(e){this.input_.value=e||"",this.updateClasses_()},L.prototype.change=L.prototype.change,L.prototype.init=function(){if(this.element_&&(this.label_=this.element_.querySelector("."+this.CssClasses_.LABEL),this.input_=this.element_.querySelector("."+this.CssClasses_.INPUT),this.input_)){this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)&&(this.maxRows=parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE),10),isNaN(this.maxRows)&&(this.maxRows=this.Constant_.NO_MAX_ROWS)),this.input_.hasAttribute("placeholder")&&this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER),this.boundUpdateClassesHandler=this.updateClasses_.bind(this),this.boundFocusHandler=this.onFocus_.bind(this),this.boundBlurHandler=this.onBlur_.bind(this),this.boundResetHandler=this.onReset_.bind(this),this.input_.addEventListener("input",this.boundUpdateClassesHandler),this.input_.addEventListener("focus",this.boundFocusHandler),this.input_.addEventListener("blur",this.boundBlurHandler),this.input_.addEventListener("reset",this.boundResetHandler),this.maxRows!==this.Constant_.NO_MAX_ROWS&&(this.boundKeyDownHandler=this.onKeyDown_.bind(this),this.input_.addEventListener("keydown",this.boundKeyDownHandler));var e=this.element_.classList.contains(this.CssClasses_.IS_INVALID);this.updateClasses_(),this.element_.classList.add(this.CssClasses_.IS_UPGRADED),e&&this.element_.classList.add(this.CssClasses_.IS_INVALID),this.input_.hasAttribute("autofocus")&&(this.element_.focus(),this.checkFocus())}},s.register({constructor:L,classAsString:"MaterialTextfield",cssClass:"mdl-js-textfield",widget:!0});var I=function(e){this.element_=e,this.init()};window.MaterialTooltip=I,I.prototype.Constant_={},I.prototype.CssClasses_={IS_ACTIVE:"is-active",BOTTOM:"mdl-tooltip--bottom",LEFT:"mdl-tooltip--left",RIGHT:"mdl-tooltip--right",TOP:"mdl-tooltip--top"},I.prototype.handleMouseEnter_=function(e){var t=e.target.getBoundingClientRect(),s=t.left+t.width/2,i=t.top+t.height/2,n=-1*(this.element_.offsetWidth/2),a=-1*(this.element_.offsetHeight/2);this.element_.classList.contains(this.CssClasses_.LEFT)||this.element_.classList.contains(this.CssClasses_.RIGHT)?(s=t.width/2,i+a<0?(this.element_.style.top="0",this.element_.style.marginTop="0"):(this.element_.style.top=i+"px",this.element_.style.marginTop=a+"px")):s+n<0?(this.element_.style.left="0",this.element_.style.marginLeft="0"):(this.element_.style.left=s+"px",this.element_.style.marginLeft=n+"px"),this.element_.classList.contains(this.CssClasses_.TOP)?this.element_.style.top=t.top-this.element_.offsetHeight-10+"px":this.element_.classList.contains(this.CssClasses_.RIGHT)?this.element_.style.left=t.left+t.width+10+"px":this.element_.classList.contains(this.CssClasses_.LEFT)?this.element_.style.left=t.left-this.element_.offsetWidth-10+"px":this.element_.style.top=t.top+t.height+10+"px",this.element_.classList.add(this.CssClasses_.IS_ACTIVE)},I.prototype.hideTooltip_=function(){this.element_.classList.remove(this.CssClasses_.IS_ACTIVE)},I.prototype.init=function(){if(this.element_){var e=this.element_.getAttribute("for")||this.element_.getAttribute("data-mdl-for");e&&(this.forElement_=document.getElementById(e)),this.forElement_&&(this.forElement_.hasAttribute("tabindex")||this.forElement_.setAttribute("tabindex","0"),this.boundMouseEnterHandler=this.handleMouseEnter_.bind(this),this.boundMouseLeaveAndScrollHandler=this.hideTooltip_.bind(this),this.forElement_.addEventListener("mouseenter",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("touchend",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("mouseleave",this.boundMouseLeaveAndScrollHandler,!1),window.addEventListener("scroll",this.boundMouseLeaveAndScrollHandler,!0),window.addEventListener("touchstart",this.boundMouseLeaveAndScrollHandler))}},s.register({constructor:I,classAsString:"MaterialTooltip",cssClass:"mdl-tooltip"});var f=function(e){this.element_=e,this.init()};window.MaterialLayout=f,f.prototype.Constant_={MAX_WIDTH:"(max-width: 1024px)",TAB_SCROLL_PIXELS:100,RESIZE_TIMEOUT:100,MENU_ICON:"",CHEVRON_LEFT:"chevron_left",CHEVRON_RIGHT:"chevron_right"},f.prototype.Keycodes_={ENTER:13,ESCAPE:27,SPACE:32},f.prototype.Mode_={STANDARD:0,SEAMED:1,WATERFALL:2,SCROLL:3},f.prototype.CssClasses_={CONTAINER:"mdl-layout__container",HEADER:"mdl-layout__header",DRAWER:"mdl-layout__drawer",CONTENT:"mdl-layout__content",DRAWER_BTN:"mdl-layout__drawer-button",ICON:"material-icons",JS_RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_CONTAINER:"mdl-layout__tab-ripple-container",RIPPLE:"mdl-ripple",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",HEADER_SEAMED:"mdl-layout__header--seamed",HEADER_WATERFALL:"mdl-layout__header--waterfall",HEADER_SCROLL:"mdl-layout__header--scroll",FIXED_HEADER:"mdl-layout--fixed-header",OBFUSCATOR:"mdl-layout__obfuscator",TAB_BAR:"mdl-layout__tab-bar",TAB_CONTAINER:"mdl-layout__tab-bar-container",TAB:"mdl-layout__tab",TAB_BAR_BUTTON:"mdl-layout__tab-bar-button",TAB_BAR_LEFT_BUTTON:"mdl-layout__tab-bar-left-button",TAB_BAR_RIGHT_BUTTON:"mdl-layout__tab-bar-right-button",PANEL:"mdl-layout__tab-panel",HAS_DRAWER:"has-drawer",HAS_TABS:"has-tabs",HAS_SCROLLING_HEADER:"has-scrolling-header",CASTING_SHADOW:"is-casting-shadow",IS_COMPACT:"is-compact",IS_SMALL_SCREEN:"is-small-screen",IS_DRAWER_OPEN:"is-visible",IS_ACTIVE:"is-active",IS_UPGRADED:"is-upgraded",IS_ANIMATING:"is-animating",ON_LARGE_SCREEN:"mdl-layout--large-screen-only",ON_SMALL_SCREEN:"mdl-layout--small-screen-only"},f.prototype.contentScrollHandler_=function(){if(!this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)){var e=!this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN)||this.element_.classList.contains(this.CssClasses_.FIXED_HEADER);this.content_.scrollTop>0&&!this.header_.classList.contains(this.CssClasses_.IS_COMPACT)?(this.header_.classList.add(this.CssClasses_.CASTING_SHADOW),this.header_.classList.add(this.CssClasses_.IS_COMPACT),e&&this.header_.classList.add(this.CssClasses_.IS_ANIMATING)):this.content_.scrollTop<=0&&this.header_.classList.contains(this.CssClasses_.IS_COMPACT)&&(this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW),this.header_.classList.remove(this.CssClasses_.IS_COMPACT),e&&this.header_.classList.add(this.CssClasses_.IS_ANIMATING))}},f.prototype.keyboardEventHandler_=function(e){e.keyCode===this.Keycodes_.ESCAPE&&this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)&&this.toggleDrawer()},f.prototype.screenSizeHandler_=function(){this.screenSizeMediaQuery_.matches?this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN):(this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN),this.drawer_&&(this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN),this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN)))},f.prototype.drawerToggleHandler_=function(e){if(e&&"keydown"===e.type){if(e.keyCode!==this.Keycodes_.SPACE&&e.keyCode!==this.Keycodes_.ENTER)return;e.preventDefault()}this.toggleDrawer()},f.prototype.headerTransitionEndHandler_=function(){this.header_.classList.remove(this.CssClasses_.IS_ANIMATING)},f.prototype.headerClickHandler_=function(){this.header_.classList.contains(this.CssClasses_.IS_COMPACT)&&(this.header_.classList.remove(this.CssClasses_.IS_COMPACT),this.header_.classList.add(this.CssClasses_.IS_ANIMATING))},f.prototype.resetTabState_=function(e){for(var t=0;t0?c.classList.add(this.CssClasses_.IS_ACTIVE):c.classList.remove(this.CssClasses_.IS_ACTIVE),this.tabBar_.scrollLeft0)return;this.setFrameCount(1);var i,n,a=e.currentTarget.getBoundingClientRect();if(0===e.clientX&&0===e.clientY)i=Math.round(a.width/2),n=Math.round(a.height/2);else{var l=e.clientX?e.clientX:e.touches[0].clientX,o=e.clientY?e.clientY:e.touches[0].clientY;i=Math.round(l-a.left),n=Math.round(o-a.top)}this.setRippleXY(i,n),this.setRippleStyles(!0),window.requestAnimationFrame(this.animFrameHandler.bind(this))}},y.prototype.upHandler_=function(e){e&&2!==e.detail&&window.setTimeout(function(){this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE)}.bind(this),0)},y.prototype.init=function(){if(this.element_){var e=this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER);this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)||(this.rippleElement_=this.element_.querySelector("."+this.CssClasses_.RIPPLE),this.frameCount_=0,this.rippleSize_=0,this.x_=0,this.y_=0,this.ignoringMouseDown_=!1,this.boundDownHandler=this.downHandler_.bind(this),this.element_.addEventListener("mousedown",this.boundDownHandler),this.element_.addEventListener("touchstart",this.boundDownHandler),this.boundUpHandler=this.upHandler_.bind(this),this.element_.addEventListener("mouseup",this.boundUpHandler),this.element_.addEventListener("mouseleave",this.boundUpHandler),this.element_.addEventListener("touchend",this.boundUpHandler),this.element_.addEventListener("blur",this.boundUpHandler),this.getFrameCount=function(){return this.frameCount_},this.setFrameCount=function(e){this.frameCount_=e},this.getRippleElement=function(){return this.rippleElement_},this.setRippleXY=function(e,t){this.x_=e,this.y_=t},this.setRippleStyles=function(t){if(null!==this.rippleElement_){var s,i,n,a="translate("+this.x_+"px, "+this.y_+"px)";t?(i=this.Constant_.INITIAL_SCALE,n=this.Constant_.INITIAL_SIZE):(i=this.Constant_.FINAL_SCALE,n=this.rippleSize_+"px",e&&(a="translate("+this.boundWidth/2+"px, "+this.boundHeight/2+"px)")),s="translate(-50%, -50%) "+a+i,this.rippleElement_.style.webkitTransform=s,this.rippleElement_.style.msTransform=s,this.rippleElement_.style.transform=s,t?this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING):this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING)}},this.animFrameHandler=function(){this.frameCount_-- >0?window.requestAnimationFrame(this.animFrameHandler.bind(this)):this.setRippleStyles(!1)})}},s.register({constructor:y,classAsString:"MaterialRipple",cssClass:"mdl-js-ripple-effect",widget:!1})}(); diff --git a/modules/material/www/material.blue_grey-teal.1.2.1.min.css b/modules/material/www/material.blue_grey-teal.1.2.1.min.css new file mode 100644 index 00000000..5450b339 --- /dev/null +++ b/modules/material/www/material.blue_grey-teal.1.2.1.min.css @@ -0,0 +1,8 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.2.1 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +@charset "UTF-8";html{color:rgba(0,0,0,.87)}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.browserupgrade{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.hidden{display:none!important}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}@media print{*,*:before,*:after,*:first-letter{background:transparent!important;color:#000!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}a,.mdl-accordion,.mdl-button,.mdl-card,.mdl-checkbox,.mdl-dropdown-menu,.mdl-icon-toggle,.mdl-item,.mdl-radio,.mdl-slider,.mdl-switch,.mdl-tabs__tab{-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:rgba(255,255,255,0)}html{width:100%;height:100%;-ms-touch-action:manipulation;touch-action:manipulation}body{width:100%;min-height:100%}main{display:block}*[hidden]{display:none!important}html,body{font-family:"Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:20px}h1,h2,h3,h4,h5,h6,p{padding:0}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400;line-height:1.35;letter-spacing:-.02em;opacity:.54;font-size:.6em}h1{font-size:56px;line-height:1.35;letter-spacing:-.02em;margin:24px 0}h1,h2{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h2{font-size:45px;line-height:48px}h2,h3{margin:24px 0}h3{font-size:34px;line-height:40px}h3,h4{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h4{font-size:24px;line-height:32px;-moz-osx-font-smoothing:grayscale;margin:24px 0 16px}h5{font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;margin:24px 0 16px}h6{font-size:16px;letter-spacing:.04em}h6,p{font-weight:400;line-height:24px}p{font-size:14px;letter-spacing:0;margin:0 0 16px}a{color:rgb(100,255,218);font-weight:500}blockquote{font-family:"Roboto","Helvetica","Arial",sans-serif;position:relative;font-size:24px;font-weight:300;font-style:italic;line-height:1.35;letter-spacing:.08em}blockquote:before{position:absolute;left:-.5em;content:'“'}blockquote:after{content:'”';margin-left:-.05em}mark{background-color:#f4ff81}dt{font-weight:700}address{font-size:12px;line-height:1;font-style:normal}address,ul,ol{font-weight:400;letter-spacing:0}ul,ol{font-size:14px;line-height:24px}.mdl-typography--display-4,.mdl-typography--display-4-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:112px;font-weight:300;line-height:1;letter-spacing:-.04em}.mdl-typography--display-4-color-contrast{opacity:.54}.mdl-typography--display-3,.mdl-typography--display-3-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:56px;font-weight:400;line-height:1.35;letter-spacing:-.02em}.mdl-typography--display-3-color-contrast{opacity:.54}.mdl-typography--display-2,.mdl-typography--display-2-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:45px;font-weight:400;line-height:48px}.mdl-typography--display-2-color-contrast{opacity:.54}.mdl-typography--display-1,.mdl-typography--display-1-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:34px;font-weight:400;line-height:40px}.mdl-typography--display-1-color-contrast{opacity:.54}.mdl-typography--headline,.mdl-typography--headline-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:24px;font-weight:400;line-height:32px;-moz-osx-font-smoothing:grayscale}.mdl-typography--headline-color-contrast{opacity:.87}.mdl-typography--title,.mdl-typography--title-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}.mdl-typography--title-color-contrast{opacity:.87}.mdl-typography--subhead,.mdl-typography--subhead-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;line-height:24px;letter-spacing:.04em}.mdl-typography--subhead-color-contrast{opacity:.87}.mdl-typography--body-2,.mdl-typography--body-2-color-contrast{font-size:14px;font-weight:700;line-height:24px;letter-spacing:0}.mdl-typography--body-2-color-contrast{opacity:.87}.mdl-typography--body-1,.mdl-typography--body-1-color-contrast{font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-color-contrast{opacity:.87}.mdl-typography--body-2-force-preferred-font,.mdl-typography--body-2-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:24px;letter-spacing:0}.mdl-typography--body-2-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--body-1-force-preferred-font,.mdl-typography--body-1-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--caption,.mdl-typography--caption-force-preferred-font{font-size:12px;font-weight:400;line-height:1;letter-spacing:0}.mdl-typography--caption-force-preferred-font{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--caption-color-contrast,.mdl-typography--caption-force-preferred-font-color-contrast{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;opacity:.54}.mdl-typography--caption-force-preferred-font-color-contrast,.mdl-typography--menu{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--menu{font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--menu-color-contrast{opacity:.87}.mdl-typography--menu-color-contrast,.mdl-typography--button,.mdl-typography--button-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--button,.mdl-typography--button-color-contrast{text-transform:uppercase}.mdl-typography--button-color-contrast{opacity:.87}.mdl-typography--text-left{text-align:left}.mdl-typography--text-right{text-align:right}.mdl-typography--text-center{text-align:center}.mdl-typography--text-justify{text-align:justify}.mdl-typography--text-nowrap{white-space:nowrap}.mdl-typography--text-lowercase{text-transform:lowercase}.mdl-typography--text-uppercase{text-transform:uppercase}.mdl-typography--text-capitalize{text-transform:capitalize}.mdl-typography--font-thin{font-weight:200!important}.mdl-typography--font-light{font-weight:300!important}.mdl-typography--font-regular{font-weight:400!important}.mdl-typography--font-medium{font-weight:500!important}.mdl-typography--font-bold{font-weight:700!important}.mdl-typography--font-black{font-weight:900!important}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased}.mdl-color-text--red{color:#f44336 !important}.mdl-color--red{background-color:#f44336 !important}.mdl-color-text--red-50{color:#ffebee !important}.mdl-color--red-50{background-color:#ffebee !important}.mdl-color-text--red-100{color:#ffcdd2 !important}.mdl-color--red-100{background-color:#ffcdd2 !important}.mdl-color-text--red-200{color:#ef9a9a !important}.mdl-color--red-200{background-color:#ef9a9a !important}.mdl-color-text--red-300{color:#e57373 !important}.mdl-color--red-300{background-color:#e57373 !important}.mdl-color-text--red-400{color:#ef5350 !important}.mdl-color--red-400{background-color:#ef5350 !important}.mdl-color-text--red-500{color:#f44336 !important}.mdl-color--red-500{background-color:#f44336 !important}.mdl-color-text--red-600{color:#e53935 !important}.mdl-color--red-600{background-color:#e53935 !important}.mdl-color-text--red-700{color:#d32f2f !important}.mdl-color--red-700{background-color:#d32f2f !important}.mdl-color-text--red-800{color:#c62828 !important}.mdl-color--red-800{background-color:#c62828 !important}.mdl-color-text--red-900{color:#b71c1c !important}.mdl-color--red-900{background-color:#b71c1c !important}.mdl-color-text--red-A100{color:#ff8a80 !important}.mdl-color--red-A100{background-color:#ff8a80 !important}.mdl-color-text--red-A200{color:#ff5252 !important}.mdl-color--red-A200{background-color:#ff5252 !important}.mdl-color-text--red-A400{color:#ff1744 !important}.mdl-color--red-A400{background-color:#ff1744 !important}.mdl-color-text--red-A700{color:#d50000 !important}.mdl-color--red-A700{background-color:#d50000 !important}.mdl-color-text--pink{color:#e91e63 !important}.mdl-color--pink{background-color:#e91e63 !important}.mdl-color-text--pink-50{color:#fce4ec !important}.mdl-color--pink-50{background-color:#fce4ec !important}.mdl-color-text--pink-100{color:#f8bbd0 !important}.mdl-color--pink-100{background-color:#f8bbd0 !important}.mdl-color-text--pink-200{color:#f48fb1 !important}.mdl-color--pink-200{background-color:#f48fb1 !important}.mdl-color-text--pink-300{color:#f06292 !important}.mdl-color--pink-300{background-color:#f06292 !important}.mdl-color-text--pink-400{color:#ec407a !important}.mdl-color--pink-400{background-color:#ec407a !important}.mdl-color-text--pink-500{color:#e91e63 !important}.mdl-color--pink-500{background-color:#e91e63 !important}.mdl-color-text--pink-600{color:#d81b60 !important}.mdl-color--pink-600{background-color:#d81b60 !important}.mdl-color-text--pink-700{color:#c2185b !important}.mdl-color--pink-700{background-color:#c2185b !important}.mdl-color-text--pink-800{color:#ad1457 !important}.mdl-color--pink-800{background-color:#ad1457 !important}.mdl-color-text--pink-900{color:#880e4f !important}.mdl-color--pink-900{background-color:#880e4f !important}.mdl-color-text--pink-A100{color:#ff80ab !important}.mdl-color--pink-A100{background-color:#ff80ab !important}.mdl-color-text--pink-A200{color:#ff4081 !important}.mdl-color--pink-A200{background-color:#ff4081 !important}.mdl-color-text--pink-A400{color:#f50057 !important}.mdl-color--pink-A400{background-color:#f50057 !important}.mdl-color-text--pink-A700{color:#c51162 !important}.mdl-color--pink-A700{background-color:#c51162 !important}.mdl-color-text--purple{color:#9c27b0 !important}.mdl-color--purple{background-color:#9c27b0 !important}.mdl-color-text--purple-50{color:#f3e5f5 !important}.mdl-color--purple-50{background-color:#f3e5f5 !important}.mdl-color-text--purple-100{color:#e1bee7 !important}.mdl-color--purple-100{background-color:#e1bee7 !important}.mdl-color-text--purple-200{color:#ce93d8 !important}.mdl-color--purple-200{background-color:#ce93d8 !important}.mdl-color-text--purple-300{color:#ba68c8 !important}.mdl-color--purple-300{background-color:#ba68c8 !important}.mdl-color-text--purple-400{color:#ab47bc !important}.mdl-color--purple-400{background-color:#ab47bc !important}.mdl-color-text--purple-500{color:#9c27b0 !important}.mdl-color--purple-500{background-color:#9c27b0 !important}.mdl-color-text--purple-600{color:#8e24aa !important}.mdl-color--purple-600{background-color:#8e24aa !important}.mdl-color-text--purple-700{color:#7b1fa2 !important}.mdl-color--purple-700{background-color:#7b1fa2 !important}.mdl-color-text--purple-800{color:#6a1b9a !important}.mdl-color--purple-800{background-color:#6a1b9a !important}.mdl-color-text--purple-900{color:#4a148c !important}.mdl-color--purple-900{background-color:#4a148c !important}.mdl-color-text--purple-A100{color:#ea80fc !important}.mdl-color--purple-A100{background-color:#ea80fc !important}.mdl-color-text--purple-A200{color:#e040fb !important}.mdl-color--purple-A200{background-color:#e040fb !important}.mdl-color-text--purple-A400{color:#d500f9 !important}.mdl-color--purple-A400{background-color:#d500f9 !important}.mdl-color-text--purple-A700{color:#a0f !important}.mdl-color--purple-A700{background-color:#a0f !important}.mdl-color-text--deep-purple{color:#673ab7 !important}.mdl-color--deep-purple{background-color:#673ab7 !important}.mdl-color-text--deep-purple-50{color:#ede7f6 !important}.mdl-color--deep-purple-50{background-color:#ede7f6 !important}.mdl-color-text--deep-purple-100{color:#d1c4e9 !important}.mdl-color--deep-purple-100{background-color:#d1c4e9 !important}.mdl-color-text--deep-purple-200{color:#b39ddb !important}.mdl-color--deep-purple-200{background-color:#b39ddb !important}.mdl-color-text--deep-purple-300{color:#9575cd !important}.mdl-color--deep-purple-300{background-color:#9575cd !important}.mdl-color-text--deep-purple-400{color:#7e57c2 !important}.mdl-color--deep-purple-400{background-color:#7e57c2 !important}.mdl-color-text--deep-purple-500{color:#673ab7 !important}.mdl-color--deep-purple-500{background-color:#673ab7 !important}.mdl-color-text--deep-purple-600{color:#5e35b1 !important}.mdl-color--deep-purple-600{background-color:#5e35b1 !important}.mdl-color-text--deep-purple-700{color:#512da8 !important}.mdl-color--deep-purple-700{background-color:#512da8 !important}.mdl-color-text--deep-purple-800{color:#4527a0 !important}.mdl-color--deep-purple-800{background-color:#4527a0 !important}.mdl-color-text--deep-purple-900{color:#311b92 !important}.mdl-color--deep-purple-900{background-color:#311b92 !important}.mdl-color-text--deep-purple-A100{color:#b388ff !important}.mdl-color--deep-purple-A100{background-color:#b388ff !important}.mdl-color-text--deep-purple-A200{color:#7c4dff !important}.mdl-color--deep-purple-A200{background-color:#7c4dff !important}.mdl-color-text--deep-purple-A400{color:#651fff !important}.mdl-color--deep-purple-A400{background-color:#651fff !important}.mdl-color-text--deep-purple-A700{color:#6200ea !important}.mdl-color--deep-purple-A700{background-color:#6200ea !important}.mdl-color-text--indigo{color:#3f51b5 !important}.mdl-color--indigo{background-color:#3f51b5 !important}.mdl-color-text--indigo-50{color:#e8eaf6 !important}.mdl-color--indigo-50{background-color:#e8eaf6 !important}.mdl-color-text--indigo-100{color:#c5cae9 !important}.mdl-color--indigo-100{background-color:#c5cae9 !important}.mdl-color-text--indigo-200{color:#9fa8da !important}.mdl-color--indigo-200{background-color:#9fa8da !important}.mdl-color-text--indigo-300{color:#7986cb !important}.mdl-color--indigo-300{background-color:#7986cb !important}.mdl-color-text--indigo-400{color:#5c6bc0 !important}.mdl-color--indigo-400{background-color:#5c6bc0 !important}.mdl-color-text--indigo-500{color:#3f51b5 !important}.mdl-color--indigo-500{background-color:#3f51b5 !important}.mdl-color-text--indigo-600{color:#3949ab !important}.mdl-color--indigo-600{background-color:#3949ab !important}.mdl-color-text--indigo-700{color:#303f9f !important}.mdl-color--indigo-700{background-color:#303f9f !important}.mdl-color-text--indigo-800{color:#283593 !important}.mdl-color--indigo-800{background-color:#283593 !important}.mdl-color-text--indigo-900{color:#1a237e !important}.mdl-color--indigo-900{background-color:#1a237e !important}.mdl-color-text--indigo-A100{color:#8c9eff !important}.mdl-color--indigo-A100{background-color:#8c9eff !important}.mdl-color-text--indigo-A200{color:#536dfe !important}.mdl-color--indigo-A200{background-color:#536dfe !important}.mdl-color-text--indigo-A400{color:#3d5afe !important}.mdl-color--indigo-A400{background-color:#3d5afe !important}.mdl-color-text--indigo-A700{color:#304ffe !important}.mdl-color--indigo-A700{background-color:#304ffe !important}.mdl-color-text--blue{color:#2196f3 !important}.mdl-color--blue{background-color:#2196f3 !important}.mdl-color-text--blue-50{color:#e3f2fd !important}.mdl-color--blue-50{background-color:#e3f2fd !important}.mdl-color-text--blue-100{color:#bbdefb !important}.mdl-color--blue-100{background-color:#bbdefb !important}.mdl-color-text--blue-200{color:#90caf9 !important}.mdl-color--blue-200{background-color:#90caf9 !important}.mdl-color-text--blue-300{color:#64b5f6 !important}.mdl-color--blue-300{background-color:#64b5f6 !important}.mdl-color-text--blue-400{color:#42a5f5 !important}.mdl-color--blue-400{background-color:#42a5f5 !important}.mdl-color-text--blue-500{color:#2196f3 !important}.mdl-color--blue-500{background-color:#2196f3 !important}.mdl-color-text--blue-600{color:#1e88e5 !important}.mdl-color--blue-600{background-color:#1e88e5 !important}.mdl-color-text--blue-700{color:#1976d2 !important}.mdl-color--blue-700{background-color:#1976d2 !important}.mdl-color-text--blue-800{color:#1565c0 !important}.mdl-color--blue-800{background-color:#1565c0 !important}.mdl-color-text--blue-900{color:#0d47a1 !important}.mdl-color--blue-900{background-color:#0d47a1 !important}.mdl-color-text--blue-A100{color:#82b1ff !important}.mdl-color--blue-A100{background-color:#82b1ff !important}.mdl-color-text--blue-A200{color:#448aff !important}.mdl-color--blue-A200{background-color:#448aff !important}.mdl-color-text--blue-A400{color:#2979ff !important}.mdl-color--blue-A400{background-color:#2979ff !important}.mdl-color-text--blue-A700{color:#2962ff !important}.mdl-color--blue-A700{background-color:#2962ff !important}.mdl-color-text--light-blue{color:#03a9f4 !important}.mdl-color--light-blue{background-color:#03a9f4 !important}.mdl-color-text--light-blue-50{color:#e1f5fe !important}.mdl-color--light-blue-50{background-color:#e1f5fe !important}.mdl-color-text--light-blue-100{color:#b3e5fc !important}.mdl-color--light-blue-100{background-color:#b3e5fc !important}.mdl-color-text--light-blue-200{color:#81d4fa !important}.mdl-color--light-blue-200{background-color:#81d4fa !important}.mdl-color-text--light-blue-300{color:#4fc3f7 !important}.mdl-color--light-blue-300{background-color:#4fc3f7 !important}.mdl-color-text--light-blue-400{color:#29b6f6 !important}.mdl-color--light-blue-400{background-color:#29b6f6 !important}.mdl-color-text--light-blue-500{color:#03a9f4 !important}.mdl-color--light-blue-500{background-color:#03a9f4 !important}.mdl-color-text--light-blue-600{color:#039be5 !important}.mdl-color--light-blue-600{background-color:#039be5 !important}.mdl-color-text--light-blue-700{color:#0288d1 !important}.mdl-color--light-blue-700{background-color:#0288d1 !important}.mdl-color-text--light-blue-800{color:#0277bd !important}.mdl-color--light-blue-800{background-color:#0277bd !important}.mdl-color-text--light-blue-900{color:#01579b !important}.mdl-color--light-blue-900{background-color:#01579b !important}.mdl-color-text--light-blue-A100{color:#80d8ff !important}.mdl-color--light-blue-A100{background-color:#80d8ff !important}.mdl-color-text--light-blue-A200{color:#40c4ff !important}.mdl-color--light-blue-A200{background-color:#40c4ff !important}.mdl-color-text--light-blue-A400{color:#00b0ff !important}.mdl-color--light-blue-A400{background-color:#00b0ff !important}.mdl-color-text--light-blue-A700{color:#0091ea !important}.mdl-color--light-blue-A700{background-color:#0091ea !important}.mdl-color-text--cyan{color:#00bcd4 !important}.mdl-color--cyan{background-color:#00bcd4 !important}.mdl-color-text--cyan-50{color:#e0f7fa !important}.mdl-color--cyan-50{background-color:#e0f7fa !important}.mdl-color-text--cyan-100{color:#b2ebf2 !important}.mdl-color--cyan-100{background-color:#b2ebf2 !important}.mdl-color-text--cyan-200{color:#80deea !important}.mdl-color--cyan-200{background-color:#80deea !important}.mdl-color-text--cyan-300{color:#4dd0e1 !important}.mdl-color--cyan-300{background-color:#4dd0e1 !important}.mdl-color-text--cyan-400{color:#26c6da !important}.mdl-color--cyan-400{background-color:#26c6da !important}.mdl-color-text--cyan-500{color:#00bcd4 !important}.mdl-color--cyan-500{background-color:#00bcd4 !important}.mdl-color-text--cyan-600{color:#00acc1 !important}.mdl-color--cyan-600{background-color:#00acc1 !important}.mdl-color-text--cyan-700{color:#0097a7 !important}.mdl-color--cyan-700{background-color:#0097a7 !important}.mdl-color-text--cyan-800{color:#00838f !important}.mdl-color--cyan-800{background-color:#00838f !important}.mdl-color-text--cyan-900{color:#006064 !important}.mdl-color--cyan-900{background-color:#006064 !important}.mdl-color-text--cyan-A100{color:#84ffff !important}.mdl-color--cyan-A100{background-color:#84ffff !important}.mdl-color-text--cyan-A200{color:#18ffff !important}.mdl-color--cyan-A200{background-color:#18ffff !important}.mdl-color-text--cyan-A400{color:#00e5ff !important}.mdl-color--cyan-A400{background-color:#00e5ff !important}.mdl-color-text--cyan-A700{color:#00b8d4 !important}.mdl-color--cyan-A700{background-color:#00b8d4 !important}.mdl-color-text--teal{color:#009688 !important}.mdl-color--teal{background-color:#009688 !important}.mdl-color-text--teal-50{color:#e0f2f1 !important}.mdl-color--teal-50{background-color:#e0f2f1 !important}.mdl-color-text--teal-100{color:#b2dfdb !important}.mdl-color--teal-100{background-color:#b2dfdb !important}.mdl-color-text--teal-200{color:#80cbc4 !important}.mdl-color--teal-200{background-color:#80cbc4 !important}.mdl-color-text--teal-300{color:#4db6ac !important}.mdl-color--teal-300{background-color:#4db6ac !important}.mdl-color-text--teal-400{color:#26a69a !important}.mdl-color--teal-400{background-color:#26a69a !important}.mdl-color-text--teal-500{color:#009688 !important}.mdl-color--teal-500{background-color:#009688 !important}.mdl-color-text--teal-600{color:#00897b !important}.mdl-color--teal-600{background-color:#00897b !important}.mdl-color-text--teal-700{color:#00796b !important}.mdl-color--teal-700{background-color:#00796b !important}.mdl-color-text--teal-800{color:#00695c !important}.mdl-color--teal-800{background-color:#00695c !important}.mdl-color-text--teal-900{color:#004d40 !important}.mdl-color--teal-900{background-color:#004d40 !important}.mdl-color-text--teal-A100{color:#a7ffeb !important}.mdl-color--teal-A100{background-color:#a7ffeb !important}.mdl-color-text--teal-A200{color:#64ffda !important}.mdl-color--teal-A200{background-color:#64ffda !important}.mdl-color-text--teal-A400{color:#1de9b6 !important}.mdl-color--teal-A400{background-color:#1de9b6 !important}.mdl-color-text--teal-A700{color:#00bfa5 !important}.mdl-color--teal-A700{background-color:#00bfa5 !important}.mdl-color-text--green{color:#4caf50 !important}.mdl-color--green{background-color:#4caf50 !important}.mdl-color-text--green-50{color:#e8f5e9 !important}.mdl-color--green-50{background-color:#e8f5e9 !important}.mdl-color-text--green-100{color:#c8e6c9 !important}.mdl-color--green-100{background-color:#c8e6c9 !important}.mdl-color-text--green-200{color:#a5d6a7 !important}.mdl-color--green-200{background-color:#a5d6a7 !important}.mdl-color-text--green-300{color:#81c784 !important}.mdl-color--green-300{background-color:#81c784 !important}.mdl-color-text--green-400{color:#66bb6a !important}.mdl-color--green-400{background-color:#66bb6a !important}.mdl-color-text--green-500{color:#4caf50 !important}.mdl-color--green-500{background-color:#4caf50 !important}.mdl-color-text--green-600{color:#43a047 !important}.mdl-color--green-600{background-color:#43a047 !important}.mdl-color-text--green-700{color:#388e3c !important}.mdl-color--green-700{background-color:#388e3c !important}.mdl-color-text--green-800{color:#2e7d32 !important}.mdl-color--green-800{background-color:#2e7d32 !important}.mdl-color-text--green-900{color:#1b5e20 !important}.mdl-color--green-900{background-color:#1b5e20 !important}.mdl-color-text--green-A100{color:#b9f6ca !important}.mdl-color--green-A100{background-color:#b9f6ca !important}.mdl-color-text--green-A200{color:#69f0ae !important}.mdl-color--green-A200{background-color:#69f0ae !important}.mdl-color-text--green-A400{color:#00e676 !important}.mdl-color--green-A400{background-color:#00e676 !important}.mdl-color-text--green-A700{color:#00c853 !important}.mdl-color--green-A700{background-color:#00c853 !important}.mdl-color-text--light-green{color:#8bc34a !important}.mdl-color--light-green{background-color:#8bc34a !important}.mdl-color-text--light-green-50{color:#f1f8e9 !important}.mdl-color--light-green-50{background-color:#f1f8e9 !important}.mdl-color-text--light-green-100{color:#dcedc8 !important}.mdl-color--light-green-100{background-color:#dcedc8 !important}.mdl-color-text--light-green-200{color:#c5e1a5 !important}.mdl-color--light-green-200{background-color:#c5e1a5 !important}.mdl-color-text--light-green-300{color:#aed581 !important}.mdl-color--light-green-300{background-color:#aed581 !important}.mdl-color-text--light-green-400{color:#9ccc65 !important}.mdl-color--light-green-400{background-color:#9ccc65 !important}.mdl-color-text--light-green-500{color:#8bc34a !important}.mdl-color--light-green-500{background-color:#8bc34a !important}.mdl-color-text--light-green-600{color:#7cb342 !important}.mdl-color--light-green-600{background-color:#7cb342 !important}.mdl-color-text--light-green-700{color:#689f38 !important}.mdl-color--light-green-700{background-color:#689f38 !important}.mdl-color-text--light-green-800{color:#558b2f !important}.mdl-color--light-green-800{background-color:#558b2f !important}.mdl-color-text--light-green-900{color:#33691e !important}.mdl-color--light-green-900{background-color:#33691e !important}.mdl-color-text--light-green-A100{color:#ccff90 !important}.mdl-color--light-green-A100{background-color:#ccff90 !important}.mdl-color-text--light-green-A200{color:#b2ff59 !important}.mdl-color--light-green-A200{background-color:#b2ff59 !important}.mdl-color-text--light-green-A400{color:#76ff03 !important}.mdl-color--light-green-A400{background-color:#76ff03 !important}.mdl-color-text--light-green-A700{color:#64dd17 !important}.mdl-color--light-green-A700{background-color:#64dd17 !important}.mdl-color-text--lime{color:#cddc39 !important}.mdl-color--lime{background-color:#cddc39 !important}.mdl-color-text--lime-50{color:#f9fbe7 !important}.mdl-color--lime-50{background-color:#f9fbe7 !important}.mdl-color-text--lime-100{color:#f0f4c3 !important}.mdl-color--lime-100{background-color:#f0f4c3 !important}.mdl-color-text--lime-200{color:#e6ee9c !important}.mdl-color--lime-200{background-color:#e6ee9c !important}.mdl-color-text--lime-300{color:#dce775 !important}.mdl-color--lime-300{background-color:#dce775 !important}.mdl-color-text--lime-400{color:#d4e157 !important}.mdl-color--lime-400{background-color:#d4e157 !important}.mdl-color-text--lime-500{color:#cddc39 !important}.mdl-color--lime-500{background-color:#cddc39 !important}.mdl-color-text--lime-600{color:#c0ca33 !important}.mdl-color--lime-600{background-color:#c0ca33 !important}.mdl-color-text--lime-700{color:#afb42b !important}.mdl-color--lime-700{background-color:#afb42b !important}.mdl-color-text--lime-800{color:#9e9d24 !important}.mdl-color--lime-800{background-color:#9e9d24 !important}.mdl-color-text--lime-900{color:#827717 !important}.mdl-color--lime-900{background-color:#827717 !important}.mdl-color-text--lime-A100{color:#f4ff81 !important}.mdl-color--lime-A100{background-color:#f4ff81 !important}.mdl-color-text--lime-A200{color:#eeff41 !important}.mdl-color--lime-A200{background-color:#eeff41 !important}.mdl-color-text--lime-A400{color:#c6ff00 !important}.mdl-color--lime-A400{background-color:#c6ff00 !important}.mdl-color-text--lime-A700{color:#aeea00 !important}.mdl-color--lime-A700{background-color:#aeea00 !important}.mdl-color-text--yellow{color:#ffeb3b !important}.mdl-color--yellow{background-color:#ffeb3b !important}.mdl-color-text--yellow-50{color:#fffde7 !important}.mdl-color--yellow-50{background-color:#fffde7 !important}.mdl-color-text--yellow-100{color:#fff9c4 !important}.mdl-color--yellow-100{background-color:#fff9c4 !important}.mdl-color-text--yellow-200{color:#fff59d !important}.mdl-color--yellow-200{background-color:#fff59d !important}.mdl-color-text--yellow-300{color:#fff176 !important}.mdl-color--yellow-300{background-color:#fff176 !important}.mdl-color-text--yellow-400{color:#ffee58 !important}.mdl-color--yellow-400{background-color:#ffee58 !important}.mdl-color-text--yellow-500{color:#ffeb3b !important}.mdl-color--yellow-500{background-color:#ffeb3b !important}.mdl-color-text--yellow-600{color:#fdd835 !important}.mdl-color--yellow-600{background-color:#fdd835 !important}.mdl-color-text--yellow-700{color:#fbc02d !important}.mdl-color--yellow-700{background-color:#fbc02d !important}.mdl-color-text--yellow-800{color:#f9a825 !important}.mdl-color--yellow-800{background-color:#f9a825 !important}.mdl-color-text--yellow-900{color:#f57f17 !important}.mdl-color--yellow-900{background-color:#f57f17 !important}.mdl-color-text--yellow-A100{color:#ffff8d !important}.mdl-color--yellow-A100{background-color:#ffff8d !important}.mdl-color-text--yellow-A200{color:#ff0 !important}.mdl-color--yellow-A200{background-color:#ff0 !important}.mdl-color-text--yellow-A400{color:#ffea00 !important}.mdl-color--yellow-A400{background-color:#ffea00 !important}.mdl-color-text--yellow-A700{color:#ffd600 !important}.mdl-color--yellow-A700{background-color:#ffd600 !important}.mdl-color-text--amber{color:#ffc107 !important}.mdl-color--amber{background-color:#ffc107 !important}.mdl-color-text--amber-50{color:#fff8e1 !important}.mdl-color--amber-50{background-color:#fff8e1 !important}.mdl-color-text--amber-100{color:#ffecb3 !important}.mdl-color--amber-100{background-color:#ffecb3 !important}.mdl-color-text--amber-200{color:#ffe082 !important}.mdl-color--amber-200{background-color:#ffe082 !important}.mdl-color-text--amber-300{color:#ffd54f !important}.mdl-color--amber-300{background-color:#ffd54f !important}.mdl-color-text--amber-400{color:#ffca28 !important}.mdl-color--amber-400{background-color:#ffca28 !important}.mdl-color-text--amber-500{color:#ffc107 !important}.mdl-color--amber-500{background-color:#ffc107 !important}.mdl-color-text--amber-600{color:#ffb300 !important}.mdl-color--amber-600{background-color:#ffb300 !important}.mdl-color-text--amber-700{color:#ffa000 !important}.mdl-color--amber-700{background-color:#ffa000 !important}.mdl-color-text--amber-800{color:#ff8f00 !important}.mdl-color--amber-800{background-color:#ff8f00 !important}.mdl-color-text--amber-900{color:#ff6f00 !important}.mdl-color--amber-900{background-color:#ff6f00 !important}.mdl-color-text--amber-A100{color:#ffe57f !important}.mdl-color--amber-A100{background-color:#ffe57f !important}.mdl-color-text--amber-A200{color:#ffd740 !important}.mdl-color--amber-A200{background-color:#ffd740 !important}.mdl-color-text--amber-A400{color:#ffc400 !important}.mdl-color--amber-A400{background-color:#ffc400 !important}.mdl-color-text--amber-A700{color:#ffab00 !important}.mdl-color--amber-A700{background-color:#ffab00 !important}.mdl-color-text--orange{color:#ff9800 !important}.mdl-color--orange{background-color:#ff9800 !important}.mdl-color-text--orange-50{color:#fff3e0 !important}.mdl-color--orange-50{background-color:#fff3e0 !important}.mdl-color-text--orange-100{color:#ffe0b2 !important}.mdl-color--orange-100{background-color:#ffe0b2 !important}.mdl-color-text--orange-200{color:#ffcc80 !important}.mdl-color--orange-200{background-color:#ffcc80 !important}.mdl-color-text--orange-300{color:#ffb74d !important}.mdl-color--orange-300{background-color:#ffb74d !important}.mdl-color-text--orange-400{color:#ffa726 !important}.mdl-color--orange-400{background-color:#ffa726 !important}.mdl-color-text--orange-500{color:#ff9800 !important}.mdl-color--orange-500{background-color:#ff9800 !important}.mdl-color-text--orange-600{color:#fb8c00 !important}.mdl-color--orange-600{background-color:#fb8c00 !important}.mdl-color-text--orange-700{color:#f57c00 !important}.mdl-color--orange-700{background-color:#f57c00 !important}.mdl-color-text--orange-800{color:#ef6c00 !important}.mdl-color--orange-800{background-color:#ef6c00 !important}.mdl-color-text--orange-900{color:#e65100 !important}.mdl-color--orange-900{background-color:#e65100 !important}.mdl-color-text--orange-A100{color:#ffd180 !important}.mdl-color--orange-A100{background-color:#ffd180 !important}.mdl-color-text--orange-A200{color:#ffab40 !important}.mdl-color--orange-A200{background-color:#ffab40 !important}.mdl-color-text--orange-A400{color:#ff9100 !important}.mdl-color--orange-A400{background-color:#ff9100 !important}.mdl-color-text--orange-A700{color:#ff6d00 !important}.mdl-color--orange-A700{background-color:#ff6d00 !important}.mdl-color-text--deep-orange{color:#ff5722 !important}.mdl-color--deep-orange{background-color:#ff5722 !important}.mdl-color-text--deep-orange-50{color:#fbe9e7 !important}.mdl-color--deep-orange-50{background-color:#fbe9e7 !important}.mdl-color-text--deep-orange-100{color:#ffccbc !important}.mdl-color--deep-orange-100{background-color:#ffccbc !important}.mdl-color-text--deep-orange-200{color:#ffab91 !important}.mdl-color--deep-orange-200{background-color:#ffab91 !important}.mdl-color-text--deep-orange-300{color:#ff8a65 !important}.mdl-color--deep-orange-300{background-color:#ff8a65 !important}.mdl-color-text--deep-orange-400{color:#ff7043 !important}.mdl-color--deep-orange-400{background-color:#ff7043 !important}.mdl-color-text--deep-orange-500{color:#ff5722 !important}.mdl-color--deep-orange-500{background-color:#ff5722 !important}.mdl-color-text--deep-orange-600{color:#f4511e !important}.mdl-color--deep-orange-600{background-color:#f4511e !important}.mdl-color-text--deep-orange-700{color:#e64a19 !important}.mdl-color--deep-orange-700{background-color:#e64a19 !important}.mdl-color-text--deep-orange-800{color:#d84315 !important}.mdl-color--deep-orange-800{background-color:#d84315 !important}.mdl-color-text--deep-orange-900{color:#bf360c !important}.mdl-color--deep-orange-900{background-color:#bf360c !important}.mdl-color-text--deep-orange-A100{color:#ff9e80 !important}.mdl-color--deep-orange-A100{background-color:#ff9e80 !important}.mdl-color-text--deep-orange-A200{color:#ff6e40 !important}.mdl-color--deep-orange-A200{background-color:#ff6e40 !important}.mdl-color-text--deep-orange-A400{color:#ff3d00 !important}.mdl-color--deep-orange-A400{background-color:#ff3d00 !important}.mdl-color-text--deep-orange-A700{color:#dd2c00 !important}.mdl-color--deep-orange-A700{background-color:#dd2c00 !important}.mdl-color-text--brown{color:#795548 !important}.mdl-color--brown{background-color:#795548 !important}.mdl-color-text--brown-50{color:#efebe9 !important}.mdl-color--brown-50{background-color:#efebe9 !important}.mdl-color-text--brown-100{color:#d7ccc8 !important}.mdl-color--brown-100{background-color:#d7ccc8 !important}.mdl-color-text--brown-200{color:#bcaaa4 !important}.mdl-color--brown-200{background-color:#bcaaa4 !important}.mdl-color-text--brown-300{color:#a1887f !important}.mdl-color--brown-300{background-color:#a1887f !important}.mdl-color-text--brown-400{color:#8d6e63 !important}.mdl-color--brown-400{background-color:#8d6e63 !important}.mdl-color-text--brown-500{color:#795548 !important}.mdl-color--brown-500{background-color:#795548 !important}.mdl-color-text--brown-600{color:#6d4c41 !important}.mdl-color--brown-600{background-color:#6d4c41 !important}.mdl-color-text--brown-700{color:#5d4037 !important}.mdl-color--brown-700{background-color:#5d4037 !important}.mdl-color-text--brown-800{color:#4e342e !important}.mdl-color--brown-800{background-color:#4e342e !important}.mdl-color-text--brown-900{color:#3e2723 !important}.mdl-color--brown-900{background-color:#3e2723 !important}.mdl-color-text--grey{color:#9e9e9e !important}.mdl-color--grey{background-color:#9e9e9e !important}.mdl-color-text--grey-50{color:#fafafa !important}.mdl-color--grey-50{background-color:#fafafa !important}.mdl-color-text--grey-100{color:#f5f5f5 !important}.mdl-color--grey-100{background-color:#f5f5f5 !important}.mdl-color-text--grey-200{color:#eee !important}.mdl-color--grey-200{background-color:#eee !important}.mdl-color-text--grey-300{color:#e0e0e0 !important}.mdl-color--grey-300{background-color:#e0e0e0 !important}.mdl-color-text--grey-400{color:#bdbdbd !important}.mdl-color--grey-400{background-color:#bdbdbd !important}.mdl-color-text--grey-500{color:#9e9e9e !important}.mdl-color--grey-500{background-color:#9e9e9e !important}.mdl-color-text--grey-600{color:#757575 !important}.mdl-color--grey-600{background-color:#757575 !important}.mdl-color-text--grey-700{color:#616161 !important}.mdl-color--grey-700{background-color:#616161 !important}.mdl-color-text--grey-800{color:#424242 !important}.mdl-color--grey-800{background-color:#424242 !important}.mdl-color-text--grey-900{color:#212121 !important}.mdl-color--grey-900{background-color:#212121 !important}.mdl-color-text--blue-grey{color:#607d8b !important}.mdl-color--blue-grey{background-color:#607d8b !important}.mdl-color-text--blue-grey-50{color:#eceff1 !important}.mdl-color--blue-grey-50{background-color:#eceff1 !important}.mdl-color-text--blue-grey-100{color:#cfd8dc !important}.mdl-color--blue-grey-100{background-color:#cfd8dc !important}.mdl-color-text--blue-grey-200{color:#b0bec5 !important}.mdl-color--blue-grey-200{background-color:#b0bec5 !important}.mdl-color-text--blue-grey-300{color:#90a4ae !important}.mdl-color--blue-grey-300{background-color:#90a4ae !important}.mdl-color-text--blue-grey-400{color:#78909c !important}.mdl-color--blue-grey-400{background-color:#78909c !important}.mdl-color-text--blue-grey-500{color:#607d8b !important}.mdl-color--blue-grey-500{background-color:#607d8b !important}.mdl-color-text--blue-grey-600{color:#546e7a !important}.mdl-color--blue-grey-600{background-color:#546e7a !important}.mdl-color-text--blue-grey-700{color:#455a64 !important}.mdl-color--blue-grey-700{background-color:#455a64 !important}.mdl-color-text--blue-grey-800{color:#37474f !important}.mdl-color--blue-grey-800{background-color:#37474f !important}.mdl-color-text--blue-grey-900{color:#263238 !important}.mdl-color--blue-grey-900{background-color:#263238 !important}.mdl-color--black{background-color:#000 !important}.mdl-color-text--black{color:#000 !important}.mdl-color--white{background-color:#fff !important}.mdl-color-text--white{color:#fff !important}.mdl-color--primary{background-color:rgb(96,125,139)!important}.mdl-color--primary-contrast{background-color:rgb(255,255,255)!important}.mdl-color--primary-dark{background-color:rgb(69,90,100)!important}.mdl-color--accent{background-color:rgb(100,255,218)!important}.mdl-color--accent-contrast{background-color:rgb(66,66,66)!important}.mdl-color-text--primary{color:rgb(96,125,139)!important}.mdl-color-text--primary-contrast{color:rgb(255,255,255)!important}.mdl-color-text--primary-dark{color:rgb(69,90,100)!important}.mdl-color-text--accent{color:rgb(100,255,218)!important}.mdl-color-text--accent-contrast{color:rgb(66,66,66)!important}.mdl-ripple{background:#000;border-radius:50%;height:50px;left:0;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:50px;overflow:hidden}.mdl-ripple.is-animating{transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1);transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1),-webkit-transform .3s cubic-bezier(0,0,.2,1)}.mdl-ripple.is-visible{opacity:.3}.mdl-animation--default,.mdl-animation--fast-out-slow-in{transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-animation--linear-out-slow-in{transition-timing-function:cubic-bezier(0,0,.2,1)}.mdl-animation--fast-out-linear-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.mdl-badge{position:relative;white-space:nowrap;margin-right:24px}.mdl-badge:not([data-badge]){margin-right:auto}.mdl-badge[data-badge]:after{content:attr(data-badge);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:absolute;top:-11px;right:-24px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:600;font-size:12px;width:22px;height:22px;border-radius:50%;background:rgb(100,255,218);color:rgb(66,66,66)}.mdl-button .mdl-badge[data-badge]:after{top:-10px;right:-5px}.mdl-badge.mdl-badge--no-background[data-badge]:after{color:rgb(100,255,218);background:rgba(66,66,66,.2);box-shadow:0 0 1px gray}.mdl-badge.mdl-badge--overlap{margin-right:10px}.mdl-badge.mdl-badge--overlap:after{right:-10px}.mdl-button{background:0 0;border:none;border-radius:2px;color:#000;position:relative;height:36px;margin:0;min-width:64px;padding:0 16px;display:inline-block;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;letter-spacing:0;overflow:hidden;will-change:box-shadow;transition:box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);outline:none;cursor:pointer;text-decoration:none;text-align:center;line-height:36px;vertical-align:middle}.mdl-button::-moz-focus-inner{border:0}.mdl-button:hover{background-color:rgba(158,158,158,.2)}.mdl-button:focus:not(:active){background-color:rgba(0,0,0,.12)}.mdl-button:active{background-color:rgba(158,158,158,.4)}.mdl-button.mdl-button--colored{color:rgb(96,125,139)}.mdl-button.mdl-button--colored:focus:not(:active){background-color:rgba(0,0,0,.12)}input.mdl-button[type="submit"]{-webkit-appearance:none}.mdl-button--raised{background:rgba(158,158,158,.2);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-button--raised:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--raised:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--raised.mdl-button--colored{background:rgb(96,125,139);color:rgb(255,255,255)}.mdl-button--raised.mdl-button--colored:hover{background-color:rgb(96,125,139)}.mdl-button--raised.mdl-button--colored:active{background-color:rgb(96,125,139)}.mdl-button--raised.mdl-button--colored:focus:not(:active){background-color:rgb(96,125,139)}.mdl-button--raised.mdl-button--colored .mdl-ripple{background:rgb(255,255,255)}.mdl-button--fab{border-radius:50%;font-size:24px;height:56px;margin:auto;min-width:56px;width:56px;padding:0;overflow:hidden;background:rgba(158,158,158,.2);box-shadow:0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);position:relative;line-height:normal}.mdl-button--fab .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--fab.mdl-button--mini-fab{height:40px;min-width:40px;width:40px}.mdl-button--fab .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button--fab:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--fab:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--fab.mdl-button--colored{background:rgb(100,255,218);color:rgb(66,66,66)}.mdl-button--fab.mdl-button--colored:hover{background-color:rgb(100,255,218)}.mdl-button--fab.mdl-button--colored:focus:not(:active){background-color:rgb(100,255,218)}.mdl-button--fab.mdl-button--colored:active{background-color:rgb(100,255,218)}.mdl-button--fab.mdl-button--colored .mdl-ripple{background:rgb(66,66,66)}.mdl-button--icon{border-radius:50%;font-size:24px;height:32px;margin-left:0;margin-right:0;min-width:32px;width:32px;padding:0;overflow:hidden;color:inherit;line-height:normal}.mdl-button--icon .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon{height:24px;min-width:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon .material-icons{top:0;left:0}.mdl-button--icon .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button__ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-button[disabled] .mdl-button__ripple-container .mdl-ripple,.mdl-button.mdl-button--disabled .mdl-button__ripple-container .mdl-ripple{background-color:transparent}.mdl-button--primary.mdl-button--primary{color:rgb(96,125,139)}.mdl-button--primary.mdl-button--primary .mdl-ripple{background:rgb(255,255,255)}.mdl-button--primary.mdl-button--primary.mdl-button--raised,.mdl-button--primary.mdl-button--primary.mdl-button--fab{color:rgb(255,255,255);background-color:rgb(96,125,139)}.mdl-button--accent.mdl-button--accent{color:rgb(100,255,218)}.mdl-button--accent.mdl-button--accent .mdl-ripple{background:rgb(66,66,66)}.mdl-button--accent.mdl-button--accent.mdl-button--raised,.mdl-button--accent.mdl-button--accent.mdl-button--fab{color:rgb(66,66,66);background-color:rgb(100,255,218)}.mdl-button[disabled][disabled],.mdl-button.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26);cursor:default;background-color:transparent}.mdl-button--fab[disabled][disabled],.mdl-button--fab.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-button--raised[disabled][disabled],.mdl-button--raised.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26);box-shadow:none}.mdl-button--colored[disabled][disabled],.mdl-button--colored.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26)}.mdl-button .material-icons{vertical-align:middle}.mdl-card{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;font-size:16px;font-weight:400;min-height:200px;overflow:hidden;width:330px;z-index:1;position:relative;background:#fff;border-radius:2px;box-sizing:border-box}.mdl-card__media{background-color:rgb(100,255,218);background-repeat:repeat;background-position:50% 50%;background-size:cover;background-origin:padding-box;background-attachment:scroll;box-sizing:border-box}.mdl-card__title{-webkit-align-items:center;-ms-flex-align:center;align-items:center;color:#000;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;line-height:normal;padding:16px;-webkit-perspective-origin:165px 56px;perspective-origin:165px 56px;-webkit-transform-origin:165px 56px;transform-origin:165px 56px;box-sizing:border-box}.mdl-card__title.mdl-card--border{border-bottom:1px solid rgba(0,0,0,.1)}.mdl-card__title-text{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;color:inherit;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:24px;font-weight:300;line-height:normal;overflow:hidden;-webkit-transform-origin:149px 48px;transform-origin:149px 48px;margin:0}.mdl-card__subtitle-text{font-size:14px;color:rgba(0,0,0,.54);margin:0}.mdl-card__supporting-text{color:rgba(0,0,0,.54);font-size:1rem;line-height:18px;overflow:hidden;padding:16px;width:90%}.mdl-card__actions{font-size:16px;line-height:normal;width:100%;background-color:transparent;padding:8px;box-sizing:border-box}.mdl-card__actions.mdl-card--border{border-top:1px solid rgba(0,0,0,.1)}.mdl-card--expand{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-card__menu{position:absolute;right:16px;top:16px}.mdl-checkbox{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0}.mdl-checkbox.is-upgraded{padding-left:24px}.mdl-checkbox__input{line-height:24px}.mdl-checkbox.is-upgraded .mdl-checkbox__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-checkbox__box-outline{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;overflow:hidden;border:2px solid rgba(0,0,0,.54);border-radius:2px;z-index:2}.mdl-checkbox.is-checked .mdl-checkbox__box-outline{border:2px solid rgb(96,125,139)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__box-outline,.mdl-checkbox.is-disabled .mdl-checkbox__box-outline{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__focus-helper{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:transparent}.mdl-checkbox.is-focused .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-checkbox.is-focused.is-checked .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(96,125,139,.26);background-color:rgba(96,125,139,.26)}.mdl-checkbox__tick-outline{position:absolute;top:0;left:0;height:100%;width:100%;-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");background:0 0;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background}.mdl-checkbox.is-checked .mdl-checkbox__tick-outline{background:rgb(96,125,139)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}fieldset[disabled] .mdl-checkbox.is-checked .mdl-checkbox__tick-outline,.mdl-checkbox.is-checked.is-disabled .mdl-checkbox__tick-outline{background:rgba(0,0,0,.26)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}.mdl-checkbox__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0}fieldset[disabled] .mdl-checkbox .mdl-checkbox__label,.mdl-checkbox.is-disabled .mdl-checkbox__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__ripple-container{position:absolute;z-index:2;top:-6px;left:-10px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-checkbox__ripple-container .mdl-ripple{background:rgb(96,125,139)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container{cursor:auto}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container .mdl-ripple,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container .mdl-ripple{background:0 0}.mdl-chip{height:32px;font-family:"Roboto","Helvetica","Arial",sans-serif;line-height:32px;padding:0 12px;border:0;border-radius:16px;background-color:#dedede;display:inline-block;color:rgba(0,0,0,.87);margin:2px 0;font-size:0;white-space:nowrap}.mdl-chip__text{font-size:13px;vertical-align:middle;display:inline-block}.mdl-chip__action{height:24px;width:24px;background:0 0;opacity:.54;cursor:pointer;padding:0;margin:0 0 0 4px;font-size:13px;text-decoration:none;color:rgba(0,0,0,.87);border:none;outline:none}.mdl-chip__action,.mdl-chip__contact{display:inline-block;vertical-align:middle;overflow:hidden;text-align:center}.mdl-chip__contact{height:32px;width:32px;border-radius:16px;margin-right:8px;font-size:18px;line-height:32px}.mdl-chip:focus{outline:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-chip:active{background-color:#d6d6d6}.mdl-chip--deletable{padding-right:4px}.mdl-chip--contact{padding-left:0}.mdl-data-table{position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff}.mdl-data-table thead{padding-bottom:3px}.mdl-data-table thead .mdl-data-table__select{margin-top:0}.mdl-data-table tbody tr{position:relative;height:48px;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background-color}.mdl-data-table tbody tr.is-selected{background-color:#e0e0e0}.mdl-data-table tbody tr:hover{background-color:#eee}.mdl-data-table td{text-align:right}.mdl-data-table th{padding:0 18px 12px 18px;text-align:right}.mdl-data-table td:first-of-type,.mdl-data-table th:first-of-type{padding-left:24px}.mdl-data-table td:last-of-type,.mdl-data-table th:last-of-type{padding-right:24px}.mdl-data-table td{position:relative;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px;box-sizing:border-box}.mdl-data-table td,.mdl-data-table td .mdl-data-table__select{vertical-align:middle}.mdl-data-table th{position:relative;vertical-align:bottom;text-overflow:ellipsis;font-weight:700;line-height:24px;letter-spacing:0;height:48px;font-size:12px;color:rgba(0,0,0,.54);padding-bottom:8px;box-sizing:border-box}.mdl-data-table th.mdl-data-table__header--sorted-ascending,.mdl-data-table th.mdl-data-table__header--sorted-descending{color:rgba(0,0,0,.87)}.mdl-data-table th.mdl-data-table__header--sorted-ascending:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:before{font-family:'Material Icons';font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased;font-size:16px;content:"\e5d8";margin-right:5px;vertical-align:sub}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover{cursor:pointer}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover:before{color:rgba(0,0,0,.26)}.mdl-data-table th.mdl-data-table__header--sorted-descending:before{content:"\e5db"}.mdl-data-table__select{width:16px}.mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric{text-align:left}.mdl-dialog{border:none;box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2);width:280px}.mdl-dialog__title{padding:24px 24px 0;margin:0;font-size:2.5rem}.mdl-dialog__actions{padding:8px 8px 8px 24px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.mdl-dialog__actions>*{margin-right:8px;height:36px}.mdl-dialog__actions>*:first-child{margin-right:0}.mdl-dialog__actions--full-width{padding:0 0 8px}.mdl-dialog__actions--full-width>*{height:48px;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;padding-right:16px;margin-right:0;text-align:right}.mdl-dialog__content{padding:20px 24px 24px;color:rgba(0,0,0,.54)}.mdl-mega-footer{padding:16px 40px;color:#9e9e9e;background-color:#424242}.mdl-mega-footer--top-section:after,.mdl-mega-footer--middle-section:after,.mdl-mega-footer--bottom-section:after,.mdl-mega-footer__top-section:after,.mdl-mega-footer__middle-section:after,.mdl-mega-footer__bottom-section:after{content:'';display:block;clear:both}.mdl-mega-footer--left-section,.mdl-mega-footer__left-section,.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{margin-bottom:16px}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:block;margin-bottom:16px;color:inherit;text-decoration:none}@media screen and (min-width:760px){.mdl-mega-footer--left-section,.mdl-mega-footer__left-section{float:left}.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{float:right}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:inline-block;margin-left:16px;line-height:36px;vertical-align:middle}}.mdl-mega-footer--social-btn,.mdl-mega-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{display:block;position:relative}@media screen and (min-width:760px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{width:33%}.mdl-mega-footer--drop-down-section:nth-child(1),.mdl-mega-footer--drop-down-section:nth-child(2),.mdl-mega-footer__drop-down-section:nth-child(1),.mdl-mega-footer__drop-down-section:nth-child(2){float:left}.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(3){float:right}.mdl-mega-footer--drop-down-section:nth-child(3):after,.mdl-mega-footer__drop-down-section:nth-child(3):after{clear:right}.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section:nth-child(4){clear:right;float:right}.mdl-mega-footer--middle-section:after,.mdl-mega-footer__middle-section:after{content:'';display:block;clear:both}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:0}}@media screen and (min-width:1024px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section,.mdl-mega-footer__drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(4){width:24%;float:left}}.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{position:absolute;width:100%;height:55.8px;padding:32px;margin:-16px 0 0;cursor:pointer;z-index:1;opacity:0}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CE'}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list{display:none}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CF'}.mdl-mega-footer--heading,.mdl-mega-footer__heading{position:relative;width:100%;padding-right:39.8px;margin-bottom:16px;box-sizing:border-box;font-size:14px;line-height:23.8px;font-weight:500;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#e0e0e0}.mdl-mega-footer--heading:after,.mdl-mega-footer__heading:after{content:'';position:absolute;top:0;right:0;display:block;width:23.8px;height:23.8px;background-size:cover}.mdl-mega-footer--link-list,.mdl-mega-footer__link-list{list-style:none;padding:0;margin:0 0 32px}.mdl-mega-footer--link-list:after,.mdl-mega-footer__link-list:after{clear:both;display:block;content:''}.mdl-mega-footer--link-list li,.mdl-mega-footer__link-list li{font-size:14px;font-weight:400;letter-spacing:0;line-height:20px}.mdl-mega-footer--link-list a,.mdl-mega-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}@media screen and (min-width:760px){.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{display:none}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{content:''}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list{display:block}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{content:''}}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:16px;margin-bottom:16px}.mdl-logo{margin-bottom:16px;color:#fff}.mdl-mega-footer--bottom-section .mdl-mega-footer--link-list li,.mdl-mega-footer__bottom-section .mdl-mega-footer__link-list li{float:left;margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-logo{float:left;margin-bottom:0;margin-right:16px}}.mdl-mini-footer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:32px 16px;color:#9e9e9e;background-color:#424242}.mdl-mini-footer:after{content:'';display:block}.mdl-mini-footer .mdl-logo{line-height:36px}.mdl-mini-footer--link-list,.mdl-mini-footer__link-list{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;list-style:none;margin:0;padding:0}.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{line-height:36px}}.mdl-mini-footer--link-list a,.mdl-mini-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}.mdl-mini-footer--left-section,.mdl-mini-footer__left-section{display:inline-block;-webkit-order:0;-ms-flex-order:0;order:0}.mdl-mini-footer--right-section,.mdl-mini-footer__right-section{display:inline-block;-webkit-order:1;-ms-flex-order:1;order:1}.mdl-mini-footer--social-btn,.mdl-mini-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-icon-toggle{position:relative;z-index:1;vertical-align:middle;display:inline-block;height:32px;margin:0;padding:0}.mdl-icon-toggle__input{line-height:32px}.mdl-icon-toggle.is-upgraded .mdl-icon-toggle__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-icon-toggle__label{display:inline-block;position:relative;cursor:pointer;height:32px;width:32px;min-width:32px;color:#616161;border-radius:50%;padding:0;margin-left:0;margin-right:0;text-align:center;background-color:transparent;will-change:background-color;transition:background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1)}.mdl-icon-toggle__label.material-icons{line-height:32px;font-size:24px}.mdl-icon-toggle.is-checked .mdl-icon-toggle__label{color:rgb(96,125,139)}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__label{color:rgba(0,0,0,.26);cursor:auto;transition:none}.mdl-icon-toggle.is-focused .mdl-icon-toggle__label{background-color:rgba(0,0,0,.12)}.mdl-icon-toggle.is-focused.is-checked .mdl-icon-toggle__label{background-color:rgba(96,125,139,.26)}.mdl-icon-toggle__ripple-container{position:absolute;z-index:2;top:-2px;left:-2px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-icon-toggle__ripple-container .mdl-ripple{background:#616161}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container{cursor:auto}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container .mdl-ripple{background:0 0}.mdl-list{display:block;padding:8px 0;list-style:none}.mdl-list__item{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:.04em;line-height:1;min-height:48px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;padding:16px;cursor:default;color:rgba(0,0,0,.87);overflow:hidden}.mdl-list__item,.mdl-list__item .mdl-list__item-primary-content{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.mdl-list__item .mdl-list__item-primary-content{-webkit-order:0;-ms-flex-order:0;order:0;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2;text-decoration:none}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-icon{margin-right:32px}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-avatar{margin-right:16px}.mdl-list__item .mdl-list__item-secondary-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column;-ms-flex-flow:column;flex-flow:column;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;margin-left:16px}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-action label{display:inline}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-info{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;color:rgba(0,0,0,.54)}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-sub-header{padding:0 0 0 16px}.mdl-list__item-icon,.mdl-list__item-icon.material-icons{height:24px;width:24px;font-size:24px;box-sizing:border-box;color:#757575}.mdl-list__item-avatar,.mdl-list__item-avatar.material-icons{height:40px;width:40px;box-sizing:border-box;border-radius:50%;background-color:#757575;font-size:40px;color:#fff}.mdl-list__item--two-line{height:72px}.mdl-list__item--two-line .mdl-list__item-primary-content{height:36px;line-height:20px;display:block}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-avatar{float:left}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left;margin-top:6px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-secondary-content{height:36px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-sub-title{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-list__item--three-line{height:88px}.mdl-list__item--three-line .mdl-list__item-primary-content{height:52px;line-height:20px;display:block}.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-avatar,.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left}.mdl-list__item--three-line .mdl-list__item-secondary-content{height:52px}.mdl-list__item--three-line .mdl-list__item-text-body{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;height:52px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-menu__container{display:block;margin:0;padding:0;border:none;position:absolute;overflow:visible;height:0;width:0;visibility:hidden;z-index:-1}.mdl-menu__container.is-visible,.mdl-menu__container.is-animating{z-index:999;visibility:visible}.mdl-menu__outline{display:block;background:#fff;margin:0;padding:0;border:none;border-radius:2px;position:absolute;top:0;left:0;overflow:hidden;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;transform-origin:0 0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);will-change:transform;transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1);z-index:-1}.mdl-menu__container.is-visible .mdl-menu__outline{opacity:1;-webkit-transform:scale(1);transform:scale(1);z-index:999}.mdl-menu__outline.mdl-menu--bottom-right{-webkit-transform-origin:100% 0;transform-origin:100% 0}.mdl-menu__outline.mdl-menu--top-left{-webkit-transform-origin:0 100%;transform-origin:0 100%}.mdl-menu__outline.mdl-menu--top-right{-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.mdl-menu{position:absolute;list-style:none;top:0;left:0;height:auto;width:auto;min-width:124px;padding:8px 0;margin:0;opacity:0;clip:rect(0 0 0 0);z-index:-1}.mdl-menu__container.is-visible .mdl-menu{opacity:1;z-index:999}.mdl-menu.is-animating{transition:opacity .2s cubic-bezier(.4,0,.2,1),clip .3s cubic-bezier(.4,0,.2,1)}.mdl-menu.mdl-menu--bottom-right{left:auto;right:0}.mdl-menu.mdl-menu--top-left{top:auto;bottom:0}.mdl-menu.mdl-menu--top-right{top:auto;left:auto;bottom:0;right:0}.mdl-menu.mdl-menu--unaligned{top:auto;left:auto}.mdl-menu__item{display:block;border:none;color:rgba(0,0,0,.87);background-color:transparent;text-align:left;margin:0;padding:0 16px;outline-color:#bdbdbd;position:relative;overflow:hidden;font-size:14px;font-weight:400;letter-spacing:0;text-decoration:none;cursor:pointer;height:48px;line-height:48px;white-space:nowrap;opacity:0;transition:opacity .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-menu__container.is-visible .mdl-menu__item{opacity:1}.mdl-menu__item::-moz-focus-inner{border:0}.mdl-menu__item--full-bleed-divider{border-bottom:1px solid rgba(0,0,0,.12)}.mdl-menu__item[disabled],.mdl-menu__item[data-mdl-disabled]{color:#bdbdbd;background-color:transparent;cursor:auto}.mdl-menu__item[disabled]:hover,.mdl-menu__item[data-mdl-disabled]:hover{background-color:transparent}.mdl-menu__item[disabled]:focus,.mdl-menu__item[data-mdl-disabled]:focus{background-color:transparent}.mdl-menu__item[disabled] .mdl-ripple,.mdl-menu__item[data-mdl-disabled] .mdl-ripple{background:0 0}.mdl-menu__item:hover{background-color:#eee}.mdl-menu__item:focus{outline:none;background-color:#eee}.mdl-menu__item:active{background-color:#e0e0e0}.mdl-menu__item--ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-progress{display:block;position:relative;height:4px;width:500px;max-width:100%}.mdl-progress>.bar{display:block;position:absolute;top:0;bottom:0;width:0%;transition:width .2s cubic-bezier(.4,0,.2,1)}.mdl-progress>.progressbar{background-color:rgb(96,125,139);z-index:1;left:0}.mdl-progress>.bufferbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(96,125,139),rgb(96,125,139));z-index:0;left:0}.mdl-progress>.auxbar{right:0}@supports (-webkit-appearance:none){.mdl-progress:not(.mdl-progress--indeterminate):not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate):not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(96,125,139),rgb(96,125,139));-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=")}}.mdl-progress:not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.9),rgba(255,255,255,.9)),linear-gradient(to right,rgb(96,125,139),rgb(96,125,139))}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1{-webkit-animation-name:indeterminate1;animation-name:indeterminate1}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1,.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-color:rgb(96,125,139);-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-image:none;-webkit-animation-name:indeterminate2;animation-name:indeterminate2}@-webkit-keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@-webkit-keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}@keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}.mdl-navigation{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;box-sizing:border-box}.mdl-navigation__link{color:#424242;text-decoration:none;margin:0;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0;opacity:.87}.mdl-navigation__link .material-icons{vertical-align:middle}.mdl-layout{width:100%;height:100%;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow-y:auto;overflow-x:hidden;position:relative;-webkit-overflow-scrolling:touch}.mdl-layout.is-small-screen .mdl-layout--large-screen-only{display:none}.mdl-layout:not(.is-small-screen) .mdl-layout--small-screen-only{display:none}.mdl-layout__container{position:absolute;width:100%;height:100%}.mdl-layout__title,.mdl-layout-title{display:block;position:relative;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400;box-sizing:border-box}.mdl-layout-spacer{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-layout__drawer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:240px;height:100%;max-height:100%;position:absolute;top:0;left:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);box-sizing:border-box;border-right:1px solid #e0e0e0;background:#fafafa;-webkit-transform:translateX(-250px);transform:translateX(-250px);-webkit-transform-style:preserve-3d;transform-style:preserve-3d;will-change:transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;color:#424242;overflow:visible;overflow-y:auto;z-index:5}.mdl-layout__drawer.is-visible{-webkit-transform:translateX(0);transform:translateX(0)}.mdl-layout__drawer.is-visible~.mdl-layout__content.mdl-layout__content{overflow:hidden}.mdl-layout__drawer>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:64px;padding-left:40px}@media screen and (max-width:1024px){.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:56px;padding-left:16px}}.mdl-layout__drawer .mdl-navigation{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-ms-flex-align:stretch;-ms-grid-row-align:stretch;align-items:stretch;padding-top:16px}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{display:block;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;padding:16px 40px;margin:0;color:#757575}@media screen and (max-width:1024px){.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{padding:16px}}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link:hover{background-color:#e0e0e0}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link--current{background-color:#e0e0e0;color:#000}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__drawer{-webkit-transform:translateX(0);transform:translateX(0)}}.mdl-layout__drawer-button{display:block;position:absolute;height:48px;width:48px;border:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden;text-align:center;cursor:pointer;font-size:26px;line-height:56px;font-family:Helvetica,Arial,sans-serif;margin:8px 12px;top:0;left:0;color:rgb(255,255,255);z-index:4}.mdl-layout__header .mdl-layout__drawer-button{position:absolute;color:rgb(255,255,255);background-color:inherit}@media screen and (max-width:1024px){.mdl-layout__header .mdl-layout__drawer-button{margin:4px}}@media screen and (max-width:1024px){.mdl-layout__drawer-button{margin:4px;color:rgba(0,0,0,.5)}}@media screen and (min-width:1025px){.mdl-layout__drawer-button{line-height:54px}.mdl-layout--no-desktop-drawer-button .mdl-layout__drawer-button,.mdl-layout--fixed-drawer>.mdl-layout__drawer-button,.mdl-layout--no-drawer-button .mdl-layout__drawer-button{display:none}}.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;box-sizing:border-box;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;width:100%;margin:0;padding:0;border:none;min-height:64px;max-height:1000px;z-index:3;background-color:rgb(96,125,139);color:rgb(255,255,255);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:max-height,box-shadow}@media screen and (max-width:1024px){.mdl-layout__header{min-height:56px}}.mdl-layout--fixed-drawer.is-upgraded:not(.is-small-screen)>.mdl-layout__header{margin-left:240px;width:calc(100% - 240px)}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__header .mdl-layout__header-row{padding-left:40px}}.mdl-layout__header>.mdl-layout-icon{position:absolute;left:40px;top:16px;height:32px;width:32px;overflow:hidden;z-index:3;display:block}@media screen and (max-width:1024px){.mdl-layout__header>.mdl-layout-icon{left:16px;top:12px}}.mdl-layout.has-drawer .mdl-layout__header>.mdl-layout-icon{display:none}.mdl-layout__header.is-compact{max-height:64px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact{max-height:56px}}.mdl-layout__header.is-compact.has-tabs{height:112px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact.has-tabs{min-height:104px}}@media screen and (max-width:1024px){.mdl-layout__header{display:none}.mdl-layout--fixed-header>.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex}}.mdl-layout__header--transparent.mdl-layout__header--transparent{background-color:transparent;box-shadow:none}.mdl-layout__header--seamed,.mdl-layout__header--scroll{box-shadow:none}.mdl-layout__header--waterfall{box-shadow:none;overflow:hidden}.mdl-layout__header--waterfall.is-casting-shadow{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-layout__header--waterfall.mdl-layout__header--waterfall-hide-top{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.mdl-layout__header-row{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:64px;margin:0;padding:0 40px 0 80px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:40px}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__header-row{padding-left:40px}}@media screen and (max-width:1024px){.mdl-layout__header-row{height:56px;padding:0 16px 0 72px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:16px}}.mdl-layout__header-row>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__header--scroll .mdl-layout__header-row{width:100%}.mdl-layout__header-row .mdl-navigation{margin:0;padding:0;height:64px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation{height:56px}}.mdl-layout__header-row .mdl-navigation__link{display:block;color:rgb(255,255,255);line-height:64px;padding:0 24px}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation__link{line-height:56px;padding:0 16px}}.mdl-layout__obfuscator{background-color:transparent;position:absolute;top:0;left:0;height:100%;width:100%;z-index:4;visibility:hidden;transition-property:background-color;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-layout__obfuscator.is-visible{background-color:rgba(0,0,0,.5);visibility:visible}@supports (pointer-events:auto){.mdl-layout__obfuscator{background-color:rgba(0,0,0,.5);opacity:0;transition-property:opacity;visibility:visible;pointer-events:none}.mdl-layout__obfuscator.is-visible{pointer-events:auto;opacity:1}}.mdl-layout__content{-ms-flex:0 1 auto;position:relative;display:inline-block;overflow-y:auto;overflow-x:hidden;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;z-index:1;-webkit-overflow-scrolling:touch}.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:240px}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow:visible}@media screen and (max-width:1024px){.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:0}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow-y:auto;overflow-x:hidden}}.mdl-layout__tab-bar{height:96px;margin:0;width:calc(100% - 112px);padding:0 0 0 56px;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:rgb(96,125,139);overflow-y:hidden;overflow-x:scroll}.mdl-layout__tab-bar::-webkit-scrollbar{display:none}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}}@media screen and (max-width:1024px){.mdl-layout__tab-bar{width:calc(100% - 60px);padding:0 0 0 60px}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{width:calc(100% - 8px);padding-left:4px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar{padding:0;overflow:hidden;width:100%}.mdl-layout__tab-bar-container{position:relative;height:48px;width:100%;border:none;margin:0;z-index:2;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden}.mdl-layout__container>.mdl-layout__tab-bar-container{position:absolute;top:0;left:0}.mdl-layout__tab-bar-button{display:inline-block;position:absolute;top:0;height:48px;width:56px;z-index:4;text-align:center;background-color:rgb(96,125,139);color:transparent;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button{width:16px}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button .material-icons,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button .material-icons{position:relative;left:-4px}@media screen and (max-width:1024px){.mdl-layout__tab-bar-button{width:60px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar-button{display:none}.mdl-layout__tab-bar-button .material-icons{line-height:48px}.mdl-layout__tab-bar-button.is-active{color:rgb(255,255,255)}.mdl-layout__tab-bar-left-button{left:0}.mdl-layout__tab-bar-right-button{right:0}.mdl-layout__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(255,255,255,.6);overflow:hidden}@media screen and (max-width:1024px){.mdl-layout__tab{padding:0 12px}}.mdl-layout--fixed-tabs .mdl-layout__tab{float:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;padding:0}.mdl-layout.is-upgraded .mdl-layout__tab.is-active{color:rgb(255,255,255)}.mdl-layout.is-upgraded .mdl-layout__tab.is-active::after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(100,255,218);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-layout__tab .mdl-layout__tab-ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-layout__tab .mdl-layout__tab-ripple-container .mdl-ripple{background-color:rgb(255,255,255)}.mdl-layout__tab-panel{display:block}.mdl-layout.is-upgraded .mdl-layout__tab-panel{display:none}.mdl-layout.is-upgraded .mdl-layout__tab-panel.is-active{display:block}.mdl-radio{position:relative;font-size:16px;line-height:24px;display:inline-block;box-sizing:border-box;margin:0;padding-left:0}.mdl-radio.is-upgraded{padding-left:24px}.mdl-radio__button{line-height:24px}.mdl-radio.is-upgraded .mdl-radio__button{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-radio__outer-circle{position:absolute;top:4px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;border:2px solid rgba(0,0,0,.54);border-radius:50%;z-index:2}.mdl-radio.is-checked .mdl-radio__outer-circle{border:2px solid rgb(96,125,139)}.mdl-radio__outer-circle fieldset[disabled] .mdl-radio,.mdl-radio.is-disabled .mdl-radio__outer-circle{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-radio__inner-circle{position:absolute;z-index:1;margin:0;top:8px;left:4px;box-sizing:border-box;width:8px;height:8px;cursor:pointer;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;-webkit-transform:scale3d(0,0,0);transform:scale3d(0,0,0);border-radius:50%;background:rgb(96,125,139)}.mdl-radio.is-checked .mdl-radio__inner-circle{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}fieldset[disabled] .mdl-radio .mdl-radio__inner-circle,.mdl-radio.is-disabled .mdl-radio__inner-circle{background:rgba(0,0,0,.26);cursor:auto}.mdl-radio.is-focused .mdl-radio__inner-circle{box-shadow:0 0 0 10px rgba(0,0,0,.1)}.mdl-radio__label{cursor:pointer}fieldset[disabled] .mdl-radio .mdl-radio__label,.mdl-radio.is-disabled .mdl-radio__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-radio__ripple-container{position:absolute;z-index:2;top:-9px;left:-13px;box-sizing:border-box;width:42px;height:42px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-radio__ripple-container .mdl-ripple{background:rgb(96,125,139)}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container,.mdl-radio.is-disabled .mdl-radio__ripple-container{cursor:auto}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container .mdl-ripple,.mdl-radio.is-disabled .mdl-radio__ripple-container .mdl-ripple{background:0 0}_:-ms-input-placeholder,:root .mdl-slider.mdl-slider.is-upgraded{-ms-appearance:none;height:32px;margin:0}.mdl-slider{width:calc(100% - 40px);margin:0 20px}.mdl-slider.is-upgraded{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:2px;background:0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:0;padding:0;color:rgb(96,125,139);-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;z-index:1;cursor:pointer}.mdl-slider.is-upgraded::-moz-focus-outer{border:0}.mdl-slider.is-upgraded::-ms-tooltip{display:none}.mdl-slider.is-upgraded::-webkit-slider-runnable-track{background:0 0}.mdl-slider.is-upgraded::-moz-range-track{background:0 0;border:none}.mdl-slider.is-upgraded::-ms-track{background:0 0;color:transparent;height:2px;width:100%;border:none}.mdl-slider.is-upgraded::-ms-fill-lower{padding:0;background:linear-gradient(to right,transparent,transparent 16px,rgb(96,125,139)16px,rgb(96,125,139)0)}.mdl-slider.is-upgraded::-ms-fill-upper{padding:0;background:linear-gradient(to left,transparent,transparent 16px,rgba(0,0,0,.26)16px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background:rgb(96,125,139);border:none;transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded::-moz-range-thumb{-moz-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background-image:none;background:rgb(96,125,139);border:none}.mdl-slider.is-upgraded:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(96,125,139,.26)}.mdl-slider.is-upgraded:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(96,125,139,.26)}.mdl-slider.is-upgraded:active::-webkit-slider-thumb{background-image:none;background:rgb(96,125,139);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded:active::-moz-range-thumb{background-image:none;background:rgb(96,125,139);transform:scale(1.5)}.mdl-slider.is-upgraded::-ms-thumb{width:32px;height:32px;border:none;border-radius:50%;background:rgb(96,125,139);transform:scale(.375);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgb(96,125,139)0%,rgb(96,125,139)37.5%,rgba(96,125,139,.26)37.5%,rgba(96,125,139,.26)100%);transform:scale(1)}.mdl-slider.is-upgraded:active::-ms-thumb{background:rgb(96,125,139);transform:scale(.5625)}.mdl-slider.is-upgraded.is-lowest-value::-webkit-slider-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-moz-range-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:active::-webkit-slider-thumb{border:1.6px solid rgba(0,0,0,.26);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:9px}.mdl-slider.is-upgraded.is-lowest-value:active::-moz-range-thumb{border:1.5px solid rgba(0,0,0,.26);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value::-ms-thumb{background:radial-gradient(circle closest-side,transparent 0%,transparent 66.67%,rgba(0,0,0,.26)66.67%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgba(0,0,0,.12)0%,rgba(0,0,0,.12)25%,rgba(0,0,0,.26)25%,rgba(0,0,0,.26)37.5%,rgba(0,0,0,.12)37.5%,rgba(0,0,0,.12)100%);transform:scale(1)}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-thumb{transform:scale(.5625);background:radial-gradient(circle closest-side,transparent 0%,transparent 77.78%,rgba(0,0,0,.26)77.78%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-lower{background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-fill-upper{margin-left:9px}.mdl-slider.is-upgraded:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled::-webkit-slider-thumb{-webkit-transform:scale(.667);transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded:disabled::-moz-range-thumb{transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-lower{background-color:rgba(0,0,0,.26);left:-6px}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-webkit-slider-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;-webkit-transform:scale(.667);transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-moz-range-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded:disabled:active::-ms-thumb,.mdl-slider.is-upgraded:disabled::-ms-thumb{transform:scale(.25);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-ms-thumb{transform:scale(.25);background:radial-gradient(circle closest-side,transparent 0%,transparent 50%,rgba(0,0,0,.26)50%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded:disabled::-ms-fill-lower{margin-right:6px;background:linear-gradient(to right,transparent,transparent 25px,rgba(0,0,0,.26)25px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded:disabled::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-fill-upper{margin-left:6px}.mdl-slider__ie-container{height:18px;overflow:visible;border:none;margin:none;padding:none}.mdl-slider__container{height:18px;position:relative;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.mdl-slider__container,.mdl-slider__background-flex{background:0 0;display:-webkit-flex;display:-ms-flexbox;display:flex}.mdl-slider__background-flex{position:absolute;height:2px;width:calc(100% - 52px);top:50%;left:0;margin:0 26px;overflow:hidden;border:0;padding:0;-webkit-transform:translate(0,-1px);transform:translate(0,-1px)}.mdl-slider__background-lower{background:rgb(96,125,139)}.mdl-slider__background-lower,.mdl-slider__background-upper{-webkit-flex:0;-ms-flex:0;flex:0;position:relative;border:0;padding:0}.mdl-slider__background-upper{background:rgba(0,0,0,.26);transition:left .18s cubic-bezier(.4,0,.2,1)}.mdl-snackbar{position:fixed;bottom:0;left:50%;cursor:default;background-color:#323232;z-index:3;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:"Roboto","Helvetica","Arial",sans-serif;will-change:transform;-webkit-transform:translate(0,80px);transform:translate(0,80px);transition:transform .25s cubic-bezier(.4,0,1,1);transition:transform .25s cubic-bezier(.4,0,1,1),-webkit-transform .25s cubic-bezier(.4,0,1,1);pointer-events:none}@media (max-width:479px){.mdl-snackbar{width:100%;left:0;min-height:48px;max-height:80px}}@media (min-width:480px){.mdl-snackbar{min-width:288px;max-width:568px;border-radius:2px;-webkit-transform:translate(-50%,80px);transform:translate(-50%,80px)}}.mdl-snackbar--active{-webkit-transform:translate(0,0);transform:translate(0,0);pointer-events:auto;transition:transform .25s cubic-bezier(0,0,.2,1);transition:transform .25s cubic-bezier(0,0,.2,1),-webkit-transform .25s cubic-bezier(0,0,.2,1)}@media (min-width:480px){.mdl-snackbar--active{-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}}.mdl-snackbar__text{padding:14px 12px 14px 24px;vertical-align:middle;color:#fff;float:left}.mdl-snackbar__action{background:0 0;border:none;color:rgb(100,255,218);float:right;padding:14px 24px 14px 12px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;line-height:1;letter-spacing:0;overflow:hidden;outline:none;opacity:0;pointer-events:none;cursor:pointer;text-decoration:none;text-align:center;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-snackbar__action::-moz-focus-inner{border:0}.mdl-snackbar__action:not([aria-hidden]){opacity:1;pointer-events:auto}.mdl-spinner{display:inline-block;position:relative;width:28px;height:28px}.mdl-spinner:not(.is-upgraded).is-active:after{content:"Loading..."}.mdl-spinner.is-upgraded.is-active{-webkit-animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite;animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite}@-webkit-keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.mdl-spinner__layer{position:absolute;width:100%;height:100%;opacity:0}.mdl-spinner__layer-1{border-color:#42a5f5}.mdl-spinner--single-color .mdl-spinner__layer-1{border-color:rgb(96,125,139)}.mdl-spinner.is-active .mdl-spinner__layer-1{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-2{border-color:#f44336}.mdl-spinner--single-color .mdl-spinner__layer-2{border-color:rgb(96,125,139)}.mdl-spinner.is-active .mdl-spinner__layer-2{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-3{border-color:#fdd835}.mdl-spinner--single-color .mdl-spinner__layer-3{border-color:rgb(96,125,139)}.mdl-spinner.is-active .mdl-spinner__layer-3{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-4{border-color:#4caf50}.mdl-spinner--single-color .mdl-spinner__layer-4{border-color:rgb(96,125,139)}.mdl-spinner.is-active .mdl-spinner__layer-4{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@-webkit-keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}@keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}.mdl-spinner__gap-patch{position:absolute;box-sizing:border-box;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__gap-patch .mdl-spinner__circle{width:1000%;left:-450%}.mdl-spinner__circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__circle-clipper .mdl-spinner__circle{width:200%}.mdl-spinner__circle{box-sizing:border-box;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent!important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0;left:0}.mdl-spinner__left .mdl-spinner__circle{border-right-color:transparent!important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.mdl-spinner.is-active .mdl-spinner__left .mdl-spinner__circle{-webkit-animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__right .mdl-spinner__circle{left:-100%;border-left-color:transparent!important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.mdl-spinner.is-active .mdl-spinner__right .mdl-spinner__circle{-webkit-animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}@keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}.mdl-switch{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0;overflow:visible;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-switch.is-upgraded{padding-left:28px}.mdl-switch__input{line-height:24px}.mdl-switch.is-upgraded .mdl-switch__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-switch__track{background:rgba(0,0,0,.26);position:absolute;left:0;top:5px;height:14px;width:36px;border-radius:14px;cursor:pointer}.mdl-switch.is-checked .mdl-switch__track{background:rgba(96,125,139,.5)}.mdl-switch__track fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__track{background:rgba(0,0,0,.12);cursor:auto}.mdl-switch__thumb{background:#fafafa;position:absolute;left:0;top:2px;height:20px;width:20px;border-radius:50%;cursor:pointer;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:left}.mdl-switch.is-checked .mdl-switch__thumb{background:rgb(96,125,139);left:16px;box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-switch__thumb fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__thumb{background:#bdbdbd;cursor:auto}.mdl-switch__focus-helper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-4px,-4px);transform:translate(-4px,-4px);display:inline-block;box-sizing:border-box;width:8px;height:8px;border-radius:50%;background-color:transparent}.mdl-switch.is-focused .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-switch.is-focused.is-checked .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(96,125,139,.26);background-color:rgba(96,125,139,.26)}.mdl-switch__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0;left:24px}.mdl-switch__label fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__label{color:#bdbdbd;cursor:auto}.mdl-switch__ripple-container{position:absolute;z-index:2;top:-12px;left:-14px;box-sizing:border-box;width:48px;height:48px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000);transition-duration:.4s;transition-timing-function:step-end;transition-property:left}.mdl-switch__ripple-container .mdl-ripple{background:rgb(96,125,139)}.mdl-switch__ripple-container fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__ripple-container{cursor:auto}fieldset[disabled] .mdl-switch .mdl-switch__ripple-container .mdl-ripple,.mdl-switch.is-disabled .mdl-switch__ripple-container .mdl-ripple{background:0 0}.mdl-switch.is-checked .mdl-switch__ripple-container{left:2px}.mdl-tabs{display:block;width:100%}.mdl-tabs__tab-bar{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:space-between;-ms-flex-line-pack:justify;align-content:space-between;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;height:48px;padding:0;margin:0;border-bottom:1px solid #e0e0e0}.mdl-tabs__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(0,0,0,.54);overflow:hidden}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active{color:rgba(0,0,0,.87)}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active:after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(96,125,139);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-tabs__tab .mdl-tabs__ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-tabs__tab .mdl-tabs__ripple-container .mdl-ripple{background:rgb(96,125,139)}.mdl-tabs__panel{display:block}.mdl-tabs.is-upgraded .mdl-tabs__panel{display:none}.mdl-tabs.is-upgraded .mdl-tabs__panel.is-active{display:block}@-webkit-keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}@keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}.mdl-textfield{position:relative;font-size:16px;display:inline-block;box-sizing:border-box;width:300px;max-width:100%;margin:0;padding:20px 0}.mdl-textfield .mdl-button{position:absolute;bottom:20px}.mdl-textfield--align-right{text-align:right}.mdl-textfield--full-width{width:100%}.mdl-textfield--expandable{min-width:32px;width:auto;min-height:32px}.mdl-textfield--expandable .mdl-button--icon{top:16px}.mdl-textfield__input{border:none;border-bottom:1px solid rgba(0,0,0,.12);display:block;font-size:16px;font-family:"Helvetica","Arial",sans-serif;margin:0;padding:4px 0;width:100%;background:0 0;text-align:left;color:inherit}.mdl-textfield__input[type="number"]{-moz-appearance:textfield}.mdl-textfield__input[type="number"]::-webkit-inner-spin-button,.mdl-textfield__input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.mdl-textfield.is-focused .mdl-textfield__input{outline:none}.mdl-textfield.is-invalid .mdl-textfield__input{border-color:#d50000;box-shadow:none}fieldset[disabled] .mdl-textfield .mdl-textfield__input,.mdl-textfield.is-disabled .mdl-textfield__input{background-color:transparent;border-bottom:1px dotted rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-textfield textarea.mdl-textfield__input{display:block}.mdl-textfield__label{bottom:0;color:rgba(0,0,0,.26);font-size:16px;left:0;right:0;pointer-events:none;position:absolute;display:block;top:24px;width:100%;overflow:hidden;white-space:nowrap;text-align:left}.mdl-textfield.is-dirty .mdl-textfield__label,.mdl-textfield.has-placeholder .mdl-textfield__label{visibility:hidden}.mdl-textfield--floating-label .mdl-textfield__label{transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{transition:none}fieldset[disabled] .mdl-textfield .mdl-textfield__label,.mdl-textfield.is-disabled.is-disabled .mdl-textfield__label{color:rgba(0,0,0,.26)}.mdl-textfield--floating-label.is-focused .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{color:rgb(96,125,139);font-size:12px;top:4px;visibility:visible}.mdl-textfield--floating-label.is-focused .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__expandable-holder .mdl-textfield__label{top:-16px}.mdl-textfield--floating-label.is-invalid .mdl-textfield__label{color:#d50000;font-size:12px}.mdl-textfield__label:after{background-color:rgb(96,125,139);bottom:20px;content:'';height:2px;left:45%;position:absolute;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);visibility:hidden;width:10px}.mdl-textfield.is-focused .mdl-textfield__label:after{left:0;visibility:visible;width:100%}.mdl-textfield.is-invalid .mdl-textfield__label:after{background-color:#d50000}.mdl-textfield__error{color:#d50000;position:absolute;font-size:12px;margin-top:3px;visibility:hidden;display:block}.mdl-textfield.is-invalid .mdl-textfield__error{visibility:visible}.mdl-textfield__expandable-holder{display:inline-block;position:relative;margin-left:32px;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-block;max-width:.1px}.mdl-textfield.is-focused .mdl-textfield__expandable-holder,.mdl-textfield.is-dirty .mdl-textfield__expandable-holder{max-width:600px}.mdl-textfield__expandable-holder .mdl-textfield__label:after{bottom:0}.mdl-tooltip{-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:top center;transform-origin:top center;z-index:999;background:rgba(97,97,97,.9);border-radius:2px;color:#fff;display:inline-block;font-size:10px;font-weight:500;line-height:14px;max-width:170px;position:fixed;top:-500px;left:-500px;padding:8px;text-align:center}.mdl-tooltip.is-active{-webkit-animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards;animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards}.mdl-tooltip--large{line-height:14px;font-size:14px;padding:16px}@-webkit-keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}@keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}.mdl-shadow--2dp{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-shadow--3dp{box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-shadow--4dp{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2)}.mdl-shadow--6dp{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.2)}.mdl-shadow--8dp{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.2)}.mdl-shadow--16dp{box-shadow:0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)}.mdl-shadow--24dp{box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2)}.mdl-grid{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;margin:0 auto;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.mdl-grid.mdl-grid--no-spacing{padding:0}.mdl-cell{box-sizing:border-box}.mdl-cell--top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.mdl-cell--middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-cell--bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.mdl-cell--stretch{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.mdl-grid.mdl-grid--no-spacing>.mdl-cell{margin:0}.mdl-cell--order-1{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12{-webkit-order:12;-ms-flex-order:12;order:12}@media (max-width:479px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:100%}.mdl-cell--hide-phone{display:none!important}.mdl-cell--order-1-phone.mdl-cell--order-1-phone{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-phone.mdl-cell--order-2-phone{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-phone.mdl-cell--order-3-phone{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-phone.mdl-cell--order-4-phone{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-phone.mdl-cell--order-5-phone{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-phone.mdl-cell--order-6-phone{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-phone.mdl-cell--order-7-phone{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-phone.mdl-cell--order-8-phone{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-phone.mdl-cell--order-9-phone{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-phone.mdl-cell--order-10-phone{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-phone.mdl-cell--order-11-phone{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-phone.mdl-cell--order-12-phone{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:25%}.mdl-cell--2-col,.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:50%}.mdl-cell--3-col,.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:75%}.mdl-cell--4-col,.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:100%}.mdl-cell--5-col,.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:100%}.mdl-cell--6-col,.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:100%}.mdl-cell--7-col,.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:100%}.mdl-cell--8-col,.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:25%}.mdl-cell--2-offset,.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:50%}.mdl-cell--3-offset,.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:75%}}@media (min-width:480px) and (max-width:839px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:50%}.mdl-cell--hide-tablet{display:none!important}.mdl-cell--order-1-tablet.mdl-cell--order-1-tablet{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-tablet.mdl-cell--order-2-tablet{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-tablet.mdl-cell--order-3-tablet{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-tablet.mdl-cell--order-4-tablet{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-tablet.mdl-cell--order-5-tablet{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-tablet.mdl-cell--order-6-tablet{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-tablet.mdl-cell--order-7-tablet{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-tablet.mdl-cell--order-8-tablet{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-tablet.mdl-cell--order-9-tablet{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-tablet.mdl-cell--order-10-tablet{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-tablet.mdl-cell--order-11-tablet{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-tablet.mdl-cell--order-12-tablet{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:calc(12.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:12.5%}.mdl-cell--2-col,.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:25%}.mdl-cell--3-col,.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:calc(37.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:37.5%}.mdl-cell--4-col,.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:50%}.mdl-cell--5-col,.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:calc(62.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:62.5%}.mdl-cell--6-col,.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:75%}.mdl-cell--7-col,.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:calc(87.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:87.5%}.mdl-cell--8-col,.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:calc(12.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:12.5%}.mdl-cell--2-offset,.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:25%}.mdl-cell--3-offset,.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:calc(37.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:37.5%}.mdl-cell--4-offset,.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:50%}.mdl-cell--5-offset,.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:calc(62.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:62.5%}.mdl-cell--6-offset,.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:75%}.mdl-cell--7-offset,.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:calc(87.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:87.5%}}@media (min-width:840px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:33.3333333333%}.mdl-cell--hide-desktop{display:none!important}.mdl-cell--order-1-desktop.mdl-cell--order-1-desktop{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-desktop.mdl-cell--order-2-desktop{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-desktop.mdl-cell--order-3-desktop{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-desktop.mdl-cell--order-4-desktop{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-desktop.mdl-cell--order-5-desktop{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-desktop.mdl-cell--order-6-desktop{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-desktop.mdl-cell--order-7-desktop{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-desktop.mdl-cell--order-8-desktop{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-desktop.mdl-cell--order-9-desktop{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-desktop.mdl-cell--order-10-desktop{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-desktop.mdl-cell--order-11-desktop{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-desktop.mdl-cell--order-12-desktop{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:calc(8.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:8.3333333333%}.mdl-cell--2-col,.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:calc(16.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:16.6666666667%}.mdl-cell--3-col,.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:25%}.mdl-cell--4-col,.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:33.3333333333%}.mdl-cell--5-col,.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:calc(41.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:41.6666666667%}.mdl-cell--6-col,.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:50%}.mdl-cell--7-col,.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:calc(58.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:58.3333333333%}.mdl-cell--8-col,.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:calc(66.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:66.6666666667%}.mdl-cell--9-col,.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:75%}.mdl-cell--10-col,.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:calc(83.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:83.3333333333%}.mdl-cell--11-col,.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:calc(91.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:91.6666666667%}.mdl-cell--12-col,.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:calc(8.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:8.3333333333%}.mdl-cell--2-offset,.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:calc(16.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:16.6666666667%}.mdl-cell--3-offset,.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:25%}.mdl-cell--4-offset,.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:calc(33.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:33.3333333333%}.mdl-cell--5-offset,.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:calc(41.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:41.6666666667%}.mdl-cell--6-offset,.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:50%}.mdl-cell--7-offset,.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:calc(58.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:58.3333333333%}.mdl-cell--8-offset,.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:calc(66.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:66.6666666667%}.mdl-cell--9-offset,.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:75%}.mdl-cell--10-offset,.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:calc(83.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:83.3333333333%}.mdl-cell--11-offset,.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:calc(91.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:91.6666666667%}}body{margin:0}.styleguide-demo h1{margin:48px 24px 0}.styleguide-demo h1:after{content:'';display:block;width:100%;border-bottom:1px solid rgba(0,0,0,.5);margin-top:24px}.styleguide-demo{opacity:0;transition:opacity .6s ease}.styleguide-masthead{height:256px;background:#212121;padding:115px 16px 0}.styleguide-container{position:relative;max-width:960px;width:100%}.styleguide-title{color:#fff;bottom:auto;position:relative;font-size:56px;font-weight:300;line-height:1;letter-spacing:-.02em}.styleguide-title:after{border-bottom:0}.styleguide-title span{font-weight:300}.mdl-styleguide .mdl-layout__drawer .mdl-navigation__link{padding:10px 24px}.demosLoaded .styleguide-demo{opacity:1}iframe{display:block;width:100%;border:none}iframe.heightSet{overflow:hidden}.demo-wrapper{margin:24px}.demo-wrapper iframe{border:1px solid rgba(0,0,0,.5)} \ No newline at end of file diff --git a/modules/material/www/material.brown-orange.1.2.1.min.css b/modules/material/www/material.brown-orange.1.2.1.min.css new file mode 100644 index 00000000..f8b76e82 --- /dev/null +++ b/modules/material/www/material.brown-orange.1.2.1.min.css @@ -0,0 +1,8 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.2.1 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +@charset "UTF-8";html{color:rgba(0,0,0,.87)}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.browserupgrade{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.hidden{display:none!important}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}@media print{*,*:before,*:after,*:first-letter{background:transparent!important;color:#000!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}a,.mdl-accordion,.mdl-button,.mdl-card,.mdl-checkbox,.mdl-dropdown-menu,.mdl-icon-toggle,.mdl-item,.mdl-radio,.mdl-slider,.mdl-switch,.mdl-tabs__tab{-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:rgba(255,255,255,0)}html{width:100%;height:100%;-ms-touch-action:manipulation;touch-action:manipulation}body{width:100%;min-height:100%}main{display:block}*[hidden]{display:none!important}html,body{font-family:"Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:20px}h1,h2,h3,h4,h5,h6,p{padding:0}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400;line-height:1.35;letter-spacing:-.02em;opacity:.54;font-size:.6em}h1{font-size:56px;line-height:1.35;letter-spacing:-.02em;margin:24px 0}h1,h2{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h2{font-size:45px;line-height:48px}h2,h3{margin:24px 0}h3{font-size:34px;line-height:40px}h3,h4{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h4{font-size:24px;line-height:32px;-moz-osx-font-smoothing:grayscale;margin:24px 0 16px}h5{font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;margin:24px 0 16px}h6{font-size:16px;letter-spacing:.04em}h6,p{font-weight:400;line-height:24px}p{font-size:14px;letter-spacing:0;margin:0 0 16px}a{color:rgb(255,171,64);font-weight:500}blockquote{font-family:"Roboto","Helvetica","Arial",sans-serif;position:relative;font-size:24px;font-weight:300;font-style:italic;line-height:1.35;letter-spacing:.08em}blockquote:before{position:absolute;left:-.5em;content:'“'}blockquote:after{content:'”';margin-left:-.05em}mark{background-color:#f4ff81}dt{font-weight:700}address{font-size:12px;line-height:1;font-style:normal}address,ul,ol{font-weight:400;letter-spacing:0}ul,ol{font-size:14px;line-height:24px}.mdl-typography--display-4,.mdl-typography--display-4-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:112px;font-weight:300;line-height:1;letter-spacing:-.04em}.mdl-typography--display-4-color-contrast{opacity:.54}.mdl-typography--display-3,.mdl-typography--display-3-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:56px;font-weight:400;line-height:1.35;letter-spacing:-.02em}.mdl-typography--display-3-color-contrast{opacity:.54}.mdl-typography--display-2,.mdl-typography--display-2-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:45px;font-weight:400;line-height:48px}.mdl-typography--display-2-color-contrast{opacity:.54}.mdl-typography--display-1,.mdl-typography--display-1-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:34px;font-weight:400;line-height:40px}.mdl-typography--display-1-color-contrast{opacity:.54}.mdl-typography--headline,.mdl-typography--headline-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:24px;font-weight:400;line-height:32px;-moz-osx-font-smoothing:grayscale}.mdl-typography--headline-color-contrast{opacity:.87}.mdl-typography--title,.mdl-typography--title-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}.mdl-typography--title-color-contrast{opacity:.87}.mdl-typography--subhead,.mdl-typography--subhead-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;line-height:24px;letter-spacing:.04em}.mdl-typography--subhead-color-contrast{opacity:.87}.mdl-typography--body-2,.mdl-typography--body-2-color-contrast{font-size:14px;font-weight:700;line-height:24px;letter-spacing:0}.mdl-typography--body-2-color-contrast{opacity:.87}.mdl-typography--body-1,.mdl-typography--body-1-color-contrast{font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-color-contrast{opacity:.87}.mdl-typography--body-2-force-preferred-font,.mdl-typography--body-2-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:24px;letter-spacing:0}.mdl-typography--body-2-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--body-1-force-preferred-font,.mdl-typography--body-1-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--caption,.mdl-typography--caption-force-preferred-font{font-size:12px;font-weight:400;line-height:1;letter-spacing:0}.mdl-typography--caption-force-preferred-font{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--caption-color-contrast,.mdl-typography--caption-force-preferred-font-color-contrast{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;opacity:.54}.mdl-typography--caption-force-preferred-font-color-contrast,.mdl-typography--menu{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--menu{font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--menu-color-contrast{opacity:.87}.mdl-typography--menu-color-contrast,.mdl-typography--button,.mdl-typography--button-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--button,.mdl-typography--button-color-contrast{text-transform:uppercase}.mdl-typography--button-color-contrast{opacity:.87}.mdl-typography--text-left{text-align:left}.mdl-typography--text-right{text-align:right}.mdl-typography--text-center{text-align:center}.mdl-typography--text-justify{text-align:justify}.mdl-typography--text-nowrap{white-space:nowrap}.mdl-typography--text-lowercase{text-transform:lowercase}.mdl-typography--text-uppercase{text-transform:uppercase}.mdl-typography--text-capitalize{text-transform:capitalize}.mdl-typography--font-thin{font-weight:200!important}.mdl-typography--font-light{font-weight:300!important}.mdl-typography--font-regular{font-weight:400!important}.mdl-typography--font-medium{font-weight:500!important}.mdl-typography--font-bold{font-weight:700!important}.mdl-typography--font-black{font-weight:900!important}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased}.mdl-color-text--red{color:#f44336 !important}.mdl-color--red{background-color:#f44336 !important}.mdl-color-text--red-50{color:#ffebee !important}.mdl-color--red-50{background-color:#ffebee !important}.mdl-color-text--red-100{color:#ffcdd2 !important}.mdl-color--red-100{background-color:#ffcdd2 !important}.mdl-color-text--red-200{color:#ef9a9a !important}.mdl-color--red-200{background-color:#ef9a9a !important}.mdl-color-text--red-300{color:#e57373 !important}.mdl-color--red-300{background-color:#e57373 !important}.mdl-color-text--red-400{color:#ef5350 !important}.mdl-color--red-400{background-color:#ef5350 !important}.mdl-color-text--red-500{color:#f44336 !important}.mdl-color--red-500{background-color:#f44336 !important}.mdl-color-text--red-600{color:#e53935 !important}.mdl-color--red-600{background-color:#e53935 !important}.mdl-color-text--red-700{color:#d32f2f !important}.mdl-color--red-700{background-color:#d32f2f !important}.mdl-color-text--red-800{color:#c62828 !important}.mdl-color--red-800{background-color:#c62828 !important}.mdl-color-text--red-900{color:#b71c1c !important}.mdl-color--red-900{background-color:#b71c1c !important}.mdl-color-text--red-A100{color:#ff8a80 !important}.mdl-color--red-A100{background-color:#ff8a80 !important}.mdl-color-text--red-A200{color:#ff5252 !important}.mdl-color--red-A200{background-color:#ff5252 !important}.mdl-color-text--red-A400{color:#ff1744 !important}.mdl-color--red-A400{background-color:#ff1744 !important}.mdl-color-text--red-A700{color:#d50000 !important}.mdl-color--red-A700{background-color:#d50000 !important}.mdl-color-text--pink{color:#e91e63 !important}.mdl-color--pink{background-color:#e91e63 !important}.mdl-color-text--pink-50{color:#fce4ec !important}.mdl-color--pink-50{background-color:#fce4ec !important}.mdl-color-text--pink-100{color:#f8bbd0 !important}.mdl-color--pink-100{background-color:#f8bbd0 !important}.mdl-color-text--pink-200{color:#f48fb1 !important}.mdl-color--pink-200{background-color:#f48fb1 !important}.mdl-color-text--pink-300{color:#f06292 !important}.mdl-color--pink-300{background-color:#f06292 !important}.mdl-color-text--pink-400{color:#ec407a !important}.mdl-color--pink-400{background-color:#ec407a !important}.mdl-color-text--pink-500{color:#e91e63 !important}.mdl-color--pink-500{background-color:#e91e63 !important}.mdl-color-text--pink-600{color:#d81b60 !important}.mdl-color--pink-600{background-color:#d81b60 !important}.mdl-color-text--pink-700{color:#c2185b !important}.mdl-color--pink-700{background-color:#c2185b !important}.mdl-color-text--pink-800{color:#ad1457 !important}.mdl-color--pink-800{background-color:#ad1457 !important}.mdl-color-text--pink-900{color:#880e4f !important}.mdl-color--pink-900{background-color:#880e4f !important}.mdl-color-text--pink-A100{color:#ff80ab !important}.mdl-color--pink-A100{background-color:#ff80ab !important}.mdl-color-text--pink-A200{color:#ff4081 !important}.mdl-color--pink-A200{background-color:#ff4081 !important}.mdl-color-text--pink-A400{color:#f50057 !important}.mdl-color--pink-A400{background-color:#f50057 !important}.mdl-color-text--pink-A700{color:#c51162 !important}.mdl-color--pink-A700{background-color:#c51162 !important}.mdl-color-text--purple{color:#9c27b0 !important}.mdl-color--purple{background-color:#9c27b0 !important}.mdl-color-text--purple-50{color:#f3e5f5 !important}.mdl-color--purple-50{background-color:#f3e5f5 !important}.mdl-color-text--purple-100{color:#e1bee7 !important}.mdl-color--purple-100{background-color:#e1bee7 !important}.mdl-color-text--purple-200{color:#ce93d8 !important}.mdl-color--purple-200{background-color:#ce93d8 !important}.mdl-color-text--purple-300{color:#ba68c8 !important}.mdl-color--purple-300{background-color:#ba68c8 !important}.mdl-color-text--purple-400{color:#ab47bc !important}.mdl-color--purple-400{background-color:#ab47bc !important}.mdl-color-text--purple-500{color:#9c27b0 !important}.mdl-color--purple-500{background-color:#9c27b0 !important}.mdl-color-text--purple-600{color:#8e24aa !important}.mdl-color--purple-600{background-color:#8e24aa !important}.mdl-color-text--purple-700{color:#7b1fa2 !important}.mdl-color--purple-700{background-color:#7b1fa2 !important}.mdl-color-text--purple-800{color:#6a1b9a !important}.mdl-color--purple-800{background-color:#6a1b9a !important}.mdl-color-text--purple-900{color:#4a148c !important}.mdl-color--purple-900{background-color:#4a148c !important}.mdl-color-text--purple-A100{color:#ea80fc !important}.mdl-color--purple-A100{background-color:#ea80fc !important}.mdl-color-text--purple-A200{color:#e040fb !important}.mdl-color--purple-A200{background-color:#e040fb !important}.mdl-color-text--purple-A400{color:#d500f9 !important}.mdl-color--purple-A400{background-color:#d500f9 !important}.mdl-color-text--purple-A700{color:#a0f !important}.mdl-color--purple-A700{background-color:#a0f !important}.mdl-color-text--deep-purple{color:#673ab7 !important}.mdl-color--deep-purple{background-color:#673ab7 !important}.mdl-color-text--deep-purple-50{color:#ede7f6 !important}.mdl-color--deep-purple-50{background-color:#ede7f6 !important}.mdl-color-text--deep-purple-100{color:#d1c4e9 !important}.mdl-color--deep-purple-100{background-color:#d1c4e9 !important}.mdl-color-text--deep-purple-200{color:#b39ddb !important}.mdl-color--deep-purple-200{background-color:#b39ddb !important}.mdl-color-text--deep-purple-300{color:#9575cd !important}.mdl-color--deep-purple-300{background-color:#9575cd !important}.mdl-color-text--deep-purple-400{color:#7e57c2 !important}.mdl-color--deep-purple-400{background-color:#7e57c2 !important}.mdl-color-text--deep-purple-500{color:#673ab7 !important}.mdl-color--deep-purple-500{background-color:#673ab7 !important}.mdl-color-text--deep-purple-600{color:#5e35b1 !important}.mdl-color--deep-purple-600{background-color:#5e35b1 !important}.mdl-color-text--deep-purple-700{color:#512da8 !important}.mdl-color--deep-purple-700{background-color:#512da8 !important}.mdl-color-text--deep-purple-800{color:#4527a0 !important}.mdl-color--deep-purple-800{background-color:#4527a0 !important}.mdl-color-text--deep-purple-900{color:#311b92 !important}.mdl-color--deep-purple-900{background-color:#311b92 !important}.mdl-color-text--deep-purple-A100{color:#b388ff !important}.mdl-color--deep-purple-A100{background-color:#b388ff !important}.mdl-color-text--deep-purple-A200{color:#7c4dff !important}.mdl-color--deep-purple-A200{background-color:#7c4dff !important}.mdl-color-text--deep-purple-A400{color:#651fff !important}.mdl-color--deep-purple-A400{background-color:#651fff !important}.mdl-color-text--deep-purple-A700{color:#6200ea !important}.mdl-color--deep-purple-A700{background-color:#6200ea !important}.mdl-color-text--indigo{color:#3f51b5 !important}.mdl-color--indigo{background-color:#3f51b5 !important}.mdl-color-text--indigo-50{color:#e8eaf6 !important}.mdl-color--indigo-50{background-color:#e8eaf6 !important}.mdl-color-text--indigo-100{color:#c5cae9 !important}.mdl-color--indigo-100{background-color:#c5cae9 !important}.mdl-color-text--indigo-200{color:#9fa8da !important}.mdl-color--indigo-200{background-color:#9fa8da !important}.mdl-color-text--indigo-300{color:#7986cb !important}.mdl-color--indigo-300{background-color:#7986cb !important}.mdl-color-text--indigo-400{color:#5c6bc0 !important}.mdl-color--indigo-400{background-color:#5c6bc0 !important}.mdl-color-text--indigo-500{color:#3f51b5 !important}.mdl-color--indigo-500{background-color:#3f51b5 !important}.mdl-color-text--indigo-600{color:#3949ab !important}.mdl-color--indigo-600{background-color:#3949ab !important}.mdl-color-text--indigo-700{color:#303f9f !important}.mdl-color--indigo-700{background-color:#303f9f !important}.mdl-color-text--indigo-800{color:#283593 !important}.mdl-color--indigo-800{background-color:#283593 !important}.mdl-color-text--indigo-900{color:#1a237e !important}.mdl-color--indigo-900{background-color:#1a237e !important}.mdl-color-text--indigo-A100{color:#8c9eff !important}.mdl-color--indigo-A100{background-color:#8c9eff !important}.mdl-color-text--indigo-A200{color:#536dfe !important}.mdl-color--indigo-A200{background-color:#536dfe !important}.mdl-color-text--indigo-A400{color:#3d5afe !important}.mdl-color--indigo-A400{background-color:#3d5afe !important}.mdl-color-text--indigo-A700{color:#304ffe !important}.mdl-color--indigo-A700{background-color:#304ffe !important}.mdl-color-text--blue{color:#2196f3 !important}.mdl-color--blue{background-color:#2196f3 !important}.mdl-color-text--blue-50{color:#e3f2fd !important}.mdl-color--blue-50{background-color:#e3f2fd !important}.mdl-color-text--blue-100{color:#bbdefb !important}.mdl-color--blue-100{background-color:#bbdefb !important}.mdl-color-text--blue-200{color:#90caf9 !important}.mdl-color--blue-200{background-color:#90caf9 !important}.mdl-color-text--blue-300{color:#64b5f6 !important}.mdl-color--blue-300{background-color:#64b5f6 !important}.mdl-color-text--blue-400{color:#42a5f5 !important}.mdl-color--blue-400{background-color:#42a5f5 !important}.mdl-color-text--blue-500{color:#2196f3 !important}.mdl-color--blue-500{background-color:#2196f3 !important}.mdl-color-text--blue-600{color:#1e88e5 !important}.mdl-color--blue-600{background-color:#1e88e5 !important}.mdl-color-text--blue-700{color:#1976d2 !important}.mdl-color--blue-700{background-color:#1976d2 !important}.mdl-color-text--blue-800{color:#1565c0 !important}.mdl-color--blue-800{background-color:#1565c0 !important}.mdl-color-text--blue-900{color:#0d47a1 !important}.mdl-color--blue-900{background-color:#0d47a1 !important}.mdl-color-text--blue-A100{color:#82b1ff !important}.mdl-color--blue-A100{background-color:#82b1ff !important}.mdl-color-text--blue-A200{color:#448aff !important}.mdl-color--blue-A200{background-color:#448aff !important}.mdl-color-text--blue-A400{color:#2979ff !important}.mdl-color--blue-A400{background-color:#2979ff !important}.mdl-color-text--blue-A700{color:#2962ff !important}.mdl-color--blue-A700{background-color:#2962ff !important}.mdl-color-text--light-blue{color:#03a9f4 !important}.mdl-color--light-blue{background-color:#03a9f4 !important}.mdl-color-text--light-blue-50{color:#e1f5fe !important}.mdl-color--light-blue-50{background-color:#e1f5fe !important}.mdl-color-text--light-blue-100{color:#b3e5fc !important}.mdl-color--light-blue-100{background-color:#b3e5fc !important}.mdl-color-text--light-blue-200{color:#81d4fa !important}.mdl-color--light-blue-200{background-color:#81d4fa !important}.mdl-color-text--light-blue-300{color:#4fc3f7 !important}.mdl-color--light-blue-300{background-color:#4fc3f7 !important}.mdl-color-text--light-blue-400{color:#29b6f6 !important}.mdl-color--light-blue-400{background-color:#29b6f6 !important}.mdl-color-text--light-blue-500{color:#03a9f4 !important}.mdl-color--light-blue-500{background-color:#03a9f4 !important}.mdl-color-text--light-blue-600{color:#039be5 !important}.mdl-color--light-blue-600{background-color:#039be5 !important}.mdl-color-text--light-blue-700{color:#0288d1 !important}.mdl-color--light-blue-700{background-color:#0288d1 !important}.mdl-color-text--light-blue-800{color:#0277bd !important}.mdl-color--light-blue-800{background-color:#0277bd !important}.mdl-color-text--light-blue-900{color:#01579b !important}.mdl-color--light-blue-900{background-color:#01579b !important}.mdl-color-text--light-blue-A100{color:#80d8ff !important}.mdl-color--light-blue-A100{background-color:#80d8ff !important}.mdl-color-text--light-blue-A200{color:#40c4ff !important}.mdl-color--light-blue-A200{background-color:#40c4ff !important}.mdl-color-text--light-blue-A400{color:#00b0ff !important}.mdl-color--light-blue-A400{background-color:#00b0ff !important}.mdl-color-text--light-blue-A700{color:#0091ea !important}.mdl-color--light-blue-A700{background-color:#0091ea !important}.mdl-color-text--cyan{color:#00bcd4 !important}.mdl-color--cyan{background-color:#00bcd4 !important}.mdl-color-text--cyan-50{color:#e0f7fa !important}.mdl-color--cyan-50{background-color:#e0f7fa !important}.mdl-color-text--cyan-100{color:#b2ebf2 !important}.mdl-color--cyan-100{background-color:#b2ebf2 !important}.mdl-color-text--cyan-200{color:#80deea !important}.mdl-color--cyan-200{background-color:#80deea !important}.mdl-color-text--cyan-300{color:#4dd0e1 !important}.mdl-color--cyan-300{background-color:#4dd0e1 !important}.mdl-color-text--cyan-400{color:#26c6da !important}.mdl-color--cyan-400{background-color:#26c6da !important}.mdl-color-text--cyan-500{color:#00bcd4 !important}.mdl-color--cyan-500{background-color:#00bcd4 !important}.mdl-color-text--cyan-600{color:#00acc1 !important}.mdl-color--cyan-600{background-color:#00acc1 !important}.mdl-color-text--cyan-700{color:#0097a7 !important}.mdl-color--cyan-700{background-color:#0097a7 !important}.mdl-color-text--cyan-800{color:#00838f !important}.mdl-color--cyan-800{background-color:#00838f !important}.mdl-color-text--cyan-900{color:#006064 !important}.mdl-color--cyan-900{background-color:#006064 !important}.mdl-color-text--cyan-A100{color:#84ffff !important}.mdl-color--cyan-A100{background-color:#84ffff !important}.mdl-color-text--cyan-A200{color:#18ffff !important}.mdl-color--cyan-A200{background-color:#18ffff !important}.mdl-color-text--cyan-A400{color:#00e5ff !important}.mdl-color--cyan-A400{background-color:#00e5ff !important}.mdl-color-text--cyan-A700{color:#00b8d4 !important}.mdl-color--cyan-A700{background-color:#00b8d4 !important}.mdl-color-text--teal{color:#009688 !important}.mdl-color--teal{background-color:#009688 !important}.mdl-color-text--teal-50{color:#e0f2f1 !important}.mdl-color--teal-50{background-color:#e0f2f1 !important}.mdl-color-text--teal-100{color:#b2dfdb !important}.mdl-color--teal-100{background-color:#b2dfdb !important}.mdl-color-text--teal-200{color:#80cbc4 !important}.mdl-color--teal-200{background-color:#80cbc4 !important}.mdl-color-text--teal-300{color:#4db6ac !important}.mdl-color--teal-300{background-color:#4db6ac !important}.mdl-color-text--teal-400{color:#26a69a !important}.mdl-color--teal-400{background-color:#26a69a !important}.mdl-color-text--teal-500{color:#009688 !important}.mdl-color--teal-500{background-color:#009688 !important}.mdl-color-text--teal-600{color:#00897b !important}.mdl-color--teal-600{background-color:#00897b !important}.mdl-color-text--teal-700{color:#00796b !important}.mdl-color--teal-700{background-color:#00796b !important}.mdl-color-text--teal-800{color:#00695c !important}.mdl-color--teal-800{background-color:#00695c !important}.mdl-color-text--teal-900{color:#004d40 !important}.mdl-color--teal-900{background-color:#004d40 !important}.mdl-color-text--teal-A100{color:#a7ffeb !important}.mdl-color--teal-A100{background-color:#a7ffeb !important}.mdl-color-text--teal-A200{color:#64ffda !important}.mdl-color--teal-A200{background-color:#64ffda !important}.mdl-color-text--teal-A400{color:#1de9b6 !important}.mdl-color--teal-A400{background-color:#1de9b6 !important}.mdl-color-text--teal-A700{color:#00bfa5 !important}.mdl-color--teal-A700{background-color:#00bfa5 !important}.mdl-color-text--green{color:#4caf50 !important}.mdl-color--green{background-color:#4caf50 !important}.mdl-color-text--green-50{color:#e8f5e9 !important}.mdl-color--green-50{background-color:#e8f5e9 !important}.mdl-color-text--green-100{color:#c8e6c9 !important}.mdl-color--green-100{background-color:#c8e6c9 !important}.mdl-color-text--green-200{color:#a5d6a7 !important}.mdl-color--green-200{background-color:#a5d6a7 !important}.mdl-color-text--green-300{color:#81c784 !important}.mdl-color--green-300{background-color:#81c784 !important}.mdl-color-text--green-400{color:#66bb6a !important}.mdl-color--green-400{background-color:#66bb6a !important}.mdl-color-text--green-500{color:#4caf50 !important}.mdl-color--green-500{background-color:#4caf50 !important}.mdl-color-text--green-600{color:#43a047 !important}.mdl-color--green-600{background-color:#43a047 !important}.mdl-color-text--green-700{color:#388e3c !important}.mdl-color--green-700{background-color:#388e3c !important}.mdl-color-text--green-800{color:#2e7d32 !important}.mdl-color--green-800{background-color:#2e7d32 !important}.mdl-color-text--green-900{color:#1b5e20 !important}.mdl-color--green-900{background-color:#1b5e20 !important}.mdl-color-text--green-A100{color:#b9f6ca !important}.mdl-color--green-A100{background-color:#b9f6ca !important}.mdl-color-text--green-A200{color:#69f0ae !important}.mdl-color--green-A200{background-color:#69f0ae !important}.mdl-color-text--green-A400{color:#00e676 !important}.mdl-color--green-A400{background-color:#00e676 !important}.mdl-color-text--green-A700{color:#00c853 !important}.mdl-color--green-A700{background-color:#00c853 !important}.mdl-color-text--light-green{color:#8bc34a !important}.mdl-color--light-green{background-color:#8bc34a !important}.mdl-color-text--light-green-50{color:#f1f8e9 !important}.mdl-color--light-green-50{background-color:#f1f8e9 !important}.mdl-color-text--light-green-100{color:#dcedc8 !important}.mdl-color--light-green-100{background-color:#dcedc8 !important}.mdl-color-text--light-green-200{color:#c5e1a5 !important}.mdl-color--light-green-200{background-color:#c5e1a5 !important}.mdl-color-text--light-green-300{color:#aed581 !important}.mdl-color--light-green-300{background-color:#aed581 !important}.mdl-color-text--light-green-400{color:#9ccc65 !important}.mdl-color--light-green-400{background-color:#9ccc65 !important}.mdl-color-text--light-green-500{color:#8bc34a !important}.mdl-color--light-green-500{background-color:#8bc34a !important}.mdl-color-text--light-green-600{color:#7cb342 !important}.mdl-color--light-green-600{background-color:#7cb342 !important}.mdl-color-text--light-green-700{color:#689f38 !important}.mdl-color--light-green-700{background-color:#689f38 !important}.mdl-color-text--light-green-800{color:#558b2f !important}.mdl-color--light-green-800{background-color:#558b2f !important}.mdl-color-text--light-green-900{color:#33691e !important}.mdl-color--light-green-900{background-color:#33691e !important}.mdl-color-text--light-green-A100{color:#ccff90 !important}.mdl-color--light-green-A100{background-color:#ccff90 !important}.mdl-color-text--light-green-A200{color:#b2ff59 !important}.mdl-color--light-green-A200{background-color:#b2ff59 !important}.mdl-color-text--light-green-A400{color:#76ff03 !important}.mdl-color--light-green-A400{background-color:#76ff03 !important}.mdl-color-text--light-green-A700{color:#64dd17 !important}.mdl-color--light-green-A700{background-color:#64dd17 !important}.mdl-color-text--lime{color:#cddc39 !important}.mdl-color--lime{background-color:#cddc39 !important}.mdl-color-text--lime-50{color:#f9fbe7 !important}.mdl-color--lime-50{background-color:#f9fbe7 !important}.mdl-color-text--lime-100{color:#f0f4c3 !important}.mdl-color--lime-100{background-color:#f0f4c3 !important}.mdl-color-text--lime-200{color:#e6ee9c !important}.mdl-color--lime-200{background-color:#e6ee9c !important}.mdl-color-text--lime-300{color:#dce775 !important}.mdl-color--lime-300{background-color:#dce775 !important}.mdl-color-text--lime-400{color:#d4e157 !important}.mdl-color--lime-400{background-color:#d4e157 !important}.mdl-color-text--lime-500{color:#cddc39 !important}.mdl-color--lime-500{background-color:#cddc39 !important}.mdl-color-text--lime-600{color:#c0ca33 !important}.mdl-color--lime-600{background-color:#c0ca33 !important}.mdl-color-text--lime-700{color:#afb42b !important}.mdl-color--lime-700{background-color:#afb42b !important}.mdl-color-text--lime-800{color:#9e9d24 !important}.mdl-color--lime-800{background-color:#9e9d24 !important}.mdl-color-text--lime-900{color:#827717 !important}.mdl-color--lime-900{background-color:#827717 !important}.mdl-color-text--lime-A100{color:#f4ff81 !important}.mdl-color--lime-A100{background-color:#f4ff81 !important}.mdl-color-text--lime-A200{color:#eeff41 !important}.mdl-color--lime-A200{background-color:#eeff41 !important}.mdl-color-text--lime-A400{color:#c6ff00 !important}.mdl-color--lime-A400{background-color:#c6ff00 !important}.mdl-color-text--lime-A700{color:#aeea00 !important}.mdl-color--lime-A700{background-color:#aeea00 !important}.mdl-color-text--yellow{color:#ffeb3b !important}.mdl-color--yellow{background-color:#ffeb3b !important}.mdl-color-text--yellow-50{color:#fffde7 !important}.mdl-color--yellow-50{background-color:#fffde7 !important}.mdl-color-text--yellow-100{color:#fff9c4 !important}.mdl-color--yellow-100{background-color:#fff9c4 !important}.mdl-color-text--yellow-200{color:#fff59d !important}.mdl-color--yellow-200{background-color:#fff59d !important}.mdl-color-text--yellow-300{color:#fff176 !important}.mdl-color--yellow-300{background-color:#fff176 !important}.mdl-color-text--yellow-400{color:#ffee58 !important}.mdl-color--yellow-400{background-color:#ffee58 !important}.mdl-color-text--yellow-500{color:#ffeb3b !important}.mdl-color--yellow-500{background-color:#ffeb3b !important}.mdl-color-text--yellow-600{color:#fdd835 !important}.mdl-color--yellow-600{background-color:#fdd835 !important}.mdl-color-text--yellow-700{color:#fbc02d !important}.mdl-color--yellow-700{background-color:#fbc02d !important}.mdl-color-text--yellow-800{color:#f9a825 !important}.mdl-color--yellow-800{background-color:#f9a825 !important}.mdl-color-text--yellow-900{color:#f57f17 !important}.mdl-color--yellow-900{background-color:#f57f17 !important}.mdl-color-text--yellow-A100{color:#ffff8d !important}.mdl-color--yellow-A100{background-color:#ffff8d !important}.mdl-color-text--yellow-A200{color:#ff0 !important}.mdl-color--yellow-A200{background-color:#ff0 !important}.mdl-color-text--yellow-A400{color:#ffea00 !important}.mdl-color--yellow-A400{background-color:#ffea00 !important}.mdl-color-text--yellow-A700{color:#ffd600 !important}.mdl-color--yellow-A700{background-color:#ffd600 !important}.mdl-color-text--amber{color:#ffc107 !important}.mdl-color--amber{background-color:#ffc107 !important}.mdl-color-text--amber-50{color:#fff8e1 !important}.mdl-color--amber-50{background-color:#fff8e1 !important}.mdl-color-text--amber-100{color:#ffecb3 !important}.mdl-color--amber-100{background-color:#ffecb3 !important}.mdl-color-text--amber-200{color:#ffe082 !important}.mdl-color--amber-200{background-color:#ffe082 !important}.mdl-color-text--amber-300{color:#ffd54f !important}.mdl-color--amber-300{background-color:#ffd54f !important}.mdl-color-text--amber-400{color:#ffca28 !important}.mdl-color--amber-400{background-color:#ffca28 !important}.mdl-color-text--amber-500{color:#ffc107 !important}.mdl-color--amber-500{background-color:#ffc107 !important}.mdl-color-text--amber-600{color:#ffb300 !important}.mdl-color--amber-600{background-color:#ffb300 !important}.mdl-color-text--amber-700{color:#ffa000 !important}.mdl-color--amber-700{background-color:#ffa000 !important}.mdl-color-text--amber-800{color:#ff8f00 !important}.mdl-color--amber-800{background-color:#ff8f00 !important}.mdl-color-text--amber-900{color:#ff6f00 !important}.mdl-color--amber-900{background-color:#ff6f00 !important}.mdl-color-text--amber-A100{color:#ffe57f !important}.mdl-color--amber-A100{background-color:#ffe57f !important}.mdl-color-text--amber-A200{color:#ffd740 !important}.mdl-color--amber-A200{background-color:#ffd740 !important}.mdl-color-text--amber-A400{color:#ffc400 !important}.mdl-color--amber-A400{background-color:#ffc400 !important}.mdl-color-text--amber-A700{color:#ffab00 !important}.mdl-color--amber-A700{background-color:#ffab00 !important}.mdl-color-text--orange{color:#ff9800 !important}.mdl-color--orange{background-color:#ff9800 !important}.mdl-color-text--orange-50{color:#fff3e0 !important}.mdl-color--orange-50{background-color:#fff3e0 !important}.mdl-color-text--orange-100{color:#ffe0b2 !important}.mdl-color--orange-100{background-color:#ffe0b2 !important}.mdl-color-text--orange-200{color:#ffcc80 !important}.mdl-color--orange-200{background-color:#ffcc80 !important}.mdl-color-text--orange-300{color:#ffb74d !important}.mdl-color--orange-300{background-color:#ffb74d !important}.mdl-color-text--orange-400{color:#ffa726 !important}.mdl-color--orange-400{background-color:#ffa726 !important}.mdl-color-text--orange-500{color:#ff9800 !important}.mdl-color--orange-500{background-color:#ff9800 !important}.mdl-color-text--orange-600{color:#fb8c00 !important}.mdl-color--orange-600{background-color:#fb8c00 !important}.mdl-color-text--orange-700{color:#f57c00 !important}.mdl-color--orange-700{background-color:#f57c00 !important}.mdl-color-text--orange-800{color:#ef6c00 !important}.mdl-color--orange-800{background-color:#ef6c00 !important}.mdl-color-text--orange-900{color:#e65100 !important}.mdl-color--orange-900{background-color:#e65100 !important}.mdl-color-text--orange-A100{color:#ffd180 !important}.mdl-color--orange-A100{background-color:#ffd180 !important}.mdl-color-text--orange-A200{color:#ffab40 !important}.mdl-color--orange-A200{background-color:#ffab40 !important}.mdl-color-text--orange-A400{color:#ff9100 !important}.mdl-color--orange-A400{background-color:#ff9100 !important}.mdl-color-text--orange-A700{color:#ff6d00 !important}.mdl-color--orange-A700{background-color:#ff6d00 !important}.mdl-color-text--deep-orange{color:#ff5722 !important}.mdl-color--deep-orange{background-color:#ff5722 !important}.mdl-color-text--deep-orange-50{color:#fbe9e7 !important}.mdl-color--deep-orange-50{background-color:#fbe9e7 !important}.mdl-color-text--deep-orange-100{color:#ffccbc !important}.mdl-color--deep-orange-100{background-color:#ffccbc !important}.mdl-color-text--deep-orange-200{color:#ffab91 !important}.mdl-color--deep-orange-200{background-color:#ffab91 !important}.mdl-color-text--deep-orange-300{color:#ff8a65 !important}.mdl-color--deep-orange-300{background-color:#ff8a65 !important}.mdl-color-text--deep-orange-400{color:#ff7043 !important}.mdl-color--deep-orange-400{background-color:#ff7043 !important}.mdl-color-text--deep-orange-500{color:#ff5722 !important}.mdl-color--deep-orange-500{background-color:#ff5722 !important}.mdl-color-text--deep-orange-600{color:#f4511e !important}.mdl-color--deep-orange-600{background-color:#f4511e !important}.mdl-color-text--deep-orange-700{color:#e64a19 !important}.mdl-color--deep-orange-700{background-color:#e64a19 !important}.mdl-color-text--deep-orange-800{color:#d84315 !important}.mdl-color--deep-orange-800{background-color:#d84315 !important}.mdl-color-text--deep-orange-900{color:#bf360c !important}.mdl-color--deep-orange-900{background-color:#bf360c !important}.mdl-color-text--deep-orange-A100{color:#ff9e80 !important}.mdl-color--deep-orange-A100{background-color:#ff9e80 !important}.mdl-color-text--deep-orange-A200{color:#ff6e40 !important}.mdl-color--deep-orange-A200{background-color:#ff6e40 !important}.mdl-color-text--deep-orange-A400{color:#ff3d00 !important}.mdl-color--deep-orange-A400{background-color:#ff3d00 !important}.mdl-color-text--deep-orange-A700{color:#dd2c00 !important}.mdl-color--deep-orange-A700{background-color:#dd2c00 !important}.mdl-color-text--brown{color:#795548 !important}.mdl-color--brown{background-color:#795548 !important}.mdl-color-text--brown-50{color:#efebe9 !important}.mdl-color--brown-50{background-color:#efebe9 !important}.mdl-color-text--brown-100{color:#d7ccc8 !important}.mdl-color--brown-100{background-color:#d7ccc8 !important}.mdl-color-text--brown-200{color:#bcaaa4 !important}.mdl-color--brown-200{background-color:#bcaaa4 !important}.mdl-color-text--brown-300{color:#a1887f !important}.mdl-color--brown-300{background-color:#a1887f !important}.mdl-color-text--brown-400{color:#8d6e63 !important}.mdl-color--brown-400{background-color:#8d6e63 !important}.mdl-color-text--brown-500{color:#795548 !important}.mdl-color--brown-500{background-color:#795548 !important}.mdl-color-text--brown-600{color:#6d4c41 !important}.mdl-color--brown-600{background-color:#6d4c41 !important}.mdl-color-text--brown-700{color:#5d4037 !important}.mdl-color--brown-700{background-color:#5d4037 !important}.mdl-color-text--brown-800{color:#4e342e !important}.mdl-color--brown-800{background-color:#4e342e !important}.mdl-color-text--brown-900{color:#3e2723 !important}.mdl-color--brown-900{background-color:#3e2723 !important}.mdl-color-text--grey{color:#9e9e9e !important}.mdl-color--grey{background-color:#9e9e9e !important}.mdl-color-text--grey-50{color:#fafafa !important}.mdl-color--grey-50{background-color:#fafafa !important}.mdl-color-text--grey-100{color:#f5f5f5 !important}.mdl-color--grey-100{background-color:#f5f5f5 !important}.mdl-color-text--grey-200{color:#eee !important}.mdl-color--grey-200{background-color:#eee !important}.mdl-color-text--grey-300{color:#e0e0e0 !important}.mdl-color--grey-300{background-color:#e0e0e0 !important}.mdl-color-text--grey-400{color:#bdbdbd !important}.mdl-color--grey-400{background-color:#bdbdbd !important}.mdl-color-text--grey-500{color:#9e9e9e !important}.mdl-color--grey-500{background-color:#9e9e9e !important}.mdl-color-text--grey-600{color:#757575 !important}.mdl-color--grey-600{background-color:#757575 !important}.mdl-color-text--grey-700{color:#616161 !important}.mdl-color--grey-700{background-color:#616161 !important}.mdl-color-text--grey-800{color:#424242 !important}.mdl-color--grey-800{background-color:#424242 !important}.mdl-color-text--grey-900{color:#212121 !important}.mdl-color--grey-900{background-color:#212121 !important}.mdl-color-text--blue-grey{color:#607d8b !important}.mdl-color--blue-grey{background-color:#607d8b !important}.mdl-color-text--blue-grey-50{color:#eceff1 !important}.mdl-color--blue-grey-50{background-color:#eceff1 !important}.mdl-color-text--blue-grey-100{color:#cfd8dc !important}.mdl-color--blue-grey-100{background-color:#cfd8dc !important}.mdl-color-text--blue-grey-200{color:#b0bec5 !important}.mdl-color--blue-grey-200{background-color:#b0bec5 !important}.mdl-color-text--blue-grey-300{color:#90a4ae !important}.mdl-color--blue-grey-300{background-color:#90a4ae !important}.mdl-color-text--blue-grey-400{color:#78909c !important}.mdl-color--blue-grey-400{background-color:#78909c !important}.mdl-color-text--blue-grey-500{color:#607d8b !important}.mdl-color--blue-grey-500{background-color:#607d8b !important}.mdl-color-text--blue-grey-600{color:#546e7a !important}.mdl-color--blue-grey-600{background-color:#546e7a !important}.mdl-color-text--blue-grey-700{color:#455a64 !important}.mdl-color--blue-grey-700{background-color:#455a64 !important}.mdl-color-text--blue-grey-800{color:#37474f !important}.mdl-color--blue-grey-800{background-color:#37474f !important}.mdl-color-text--blue-grey-900{color:#263238 !important}.mdl-color--blue-grey-900{background-color:#263238 !important}.mdl-color--black{background-color:#000 !important}.mdl-color-text--black{color:#000 !important}.mdl-color--white{background-color:#fff !important}.mdl-color-text--white{color:#fff !important}.mdl-color--primary{background-color:rgb(121,85,72)!important}.mdl-color--primary-contrast{background-color:rgb(255,255,255)!important}.mdl-color--primary-dark{background-color:rgb(93,64,55)!important}.mdl-color--accent{background-color:rgb(255,171,64)!important}.mdl-color--accent-contrast{background-color:rgb(66,66,66)!important}.mdl-color-text--primary{color:rgb(121,85,72)!important}.mdl-color-text--primary-contrast{color:rgb(255,255,255)!important}.mdl-color-text--primary-dark{color:rgb(93,64,55)!important}.mdl-color-text--accent{color:rgb(255,171,64)!important}.mdl-color-text--accent-contrast{color:rgb(66,66,66)!important}.mdl-ripple{background:#000;border-radius:50%;height:50px;left:0;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:50px;overflow:hidden}.mdl-ripple.is-animating{transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1);transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1),-webkit-transform .3s cubic-bezier(0,0,.2,1)}.mdl-ripple.is-visible{opacity:.3}.mdl-animation--default,.mdl-animation--fast-out-slow-in{transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-animation--linear-out-slow-in{transition-timing-function:cubic-bezier(0,0,.2,1)}.mdl-animation--fast-out-linear-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.mdl-badge{position:relative;white-space:nowrap;margin-right:24px}.mdl-badge:not([data-badge]){margin-right:auto}.mdl-badge[data-badge]:after{content:attr(data-badge);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:absolute;top:-11px;right:-24px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:600;font-size:12px;width:22px;height:22px;border-radius:50%;background:rgb(255,171,64);color:rgb(66,66,66)}.mdl-button .mdl-badge[data-badge]:after{top:-10px;right:-5px}.mdl-badge.mdl-badge--no-background[data-badge]:after{color:rgb(255,171,64);background:rgba(66,66,66,.2);box-shadow:0 0 1px gray}.mdl-badge.mdl-badge--overlap{margin-right:10px}.mdl-badge.mdl-badge--overlap:after{right:-10px}.mdl-button{background:0 0;border:none;border-radius:2px;color:#000;position:relative;height:36px;margin:0;min-width:64px;padding:0 16px;display:inline-block;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;letter-spacing:0;overflow:hidden;will-change:box-shadow;transition:box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);outline:none;cursor:pointer;text-decoration:none;text-align:center;line-height:36px;vertical-align:middle}.mdl-button::-moz-focus-inner{border:0}.mdl-button:hover{background-color:rgba(158,158,158,.2)}.mdl-button:focus:not(:active){background-color:rgba(0,0,0,.12)}.mdl-button:active{background-color:rgba(158,158,158,.4)}.mdl-button.mdl-button--colored{color:rgb(121,85,72)}.mdl-button.mdl-button--colored:focus:not(:active){background-color:rgba(0,0,0,.12)}input.mdl-button[type="submit"]{-webkit-appearance:none}.mdl-button--raised{background:rgba(158,158,158,.2);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-button--raised:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--raised:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--raised.mdl-button--colored{background:rgb(121,85,72);color:rgb(255,255,255)}.mdl-button--raised.mdl-button--colored:hover{background-color:rgb(121,85,72)}.mdl-button--raised.mdl-button--colored:active{background-color:rgb(121,85,72)}.mdl-button--raised.mdl-button--colored:focus:not(:active){background-color:rgb(121,85,72)}.mdl-button--raised.mdl-button--colored .mdl-ripple{background:rgb(255,255,255)}.mdl-button--fab{border-radius:50%;font-size:24px;height:56px;margin:auto;min-width:56px;width:56px;padding:0;overflow:hidden;background:rgba(158,158,158,.2);box-shadow:0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);position:relative;line-height:normal}.mdl-button--fab .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--fab.mdl-button--mini-fab{height:40px;min-width:40px;width:40px}.mdl-button--fab .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button--fab:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--fab:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--fab.mdl-button--colored{background:rgb(255,171,64);color:rgb(66,66,66)}.mdl-button--fab.mdl-button--colored:hover{background-color:rgb(255,171,64)}.mdl-button--fab.mdl-button--colored:focus:not(:active){background-color:rgb(255,171,64)}.mdl-button--fab.mdl-button--colored:active{background-color:rgb(255,171,64)}.mdl-button--fab.mdl-button--colored .mdl-ripple{background:rgb(66,66,66)}.mdl-button--icon{border-radius:50%;font-size:24px;height:32px;margin-left:0;margin-right:0;min-width:32px;width:32px;padding:0;overflow:hidden;color:inherit;line-height:normal}.mdl-button--icon .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon{height:24px;min-width:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon .material-icons{top:0;left:0}.mdl-button--icon .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button__ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-button[disabled] .mdl-button__ripple-container .mdl-ripple,.mdl-button.mdl-button--disabled .mdl-button__ripple-container .mdl-ripple{background-color:transparent}.mdl-button--primary.mdl-button--primary{color:rgb(121,85,72)}.mdl-button--primary.mdl-button--primary .mdl-ripple{background:rgb(255,255,255)}.mdl-button--primary.mdl-button--primary.mdl-button--raised,.mdl-button--primary.mdl-button--primary.mdl-button--fab{color:rgb(255,255,255);background-color:rgb(121,85,72)}.mdl-button--accent.mdl-button--accent{color:rgb(255,171,64)}.mdl-button--accent.mdl-button--accent .mdl-ripple{background:rgb(66,66,66)}.mdl-button--accent.mdl-button--accent.mdl-button--raised,.mdl-button--accent.mdl-button--accent.mdl-button--fab{color:rgb(66,66,66);background-color:rgb(255,171,64)}.mdl-button[disabled][disabled],.mdl-button.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26);cursor:default;background-color:transparent}.mdl-button--fab[disabled][disabled],.mdl-button--fab.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-button--raised[disabled][disabled],.mdl-button--raised.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26);box-shadow:none}.mdl-button--colored[disabled][disabled],.mdl-button--colored.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26)}.mdl-button .material-icons{vertical-align:middle}.mdl-card{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;font-size:16px;font-weight:400;min-height:200px;overflow:hidden;width:330px;z-index:1;position:relative;background:#fff;border-radius:2px;box-sizing:border-box}.mdl-card__media{background-color:rgb(255,171,64);background-repeat:repeat;background-position:50% 50%;background-size:cover;background-origin:padding-box;background-attachment:scroll;box-sizing:border-box}.mdl-card__title{-webkit-align-items:center;-ms-flex-align:center;align-items:center;color:#000;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;line-height:normal;padding:16px;-webkit-perspective-origin:165px 56px;perspective-origin:165px 56px;-webkit-transform-origin:165px 56px;transform-origin:165px 56px;box-sizing:border-box}.mdl-card__title.mdl-card--border{border-bottom:1px solid rgba(0,0,0,.1)}.mdl-card__title-text{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;color:inherit;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:24px;font-weight:300;line-height:normal;overflow:hidden;-webkit-transform-origin:149px 48px;transform-origin:149px 48px;margin:0}.mdl-card__subtitle-text{font-size:14px;color:rgba(0,0,0,.54);margin:0}.mdl-card__supporting-text{color:rgba(0,0,0,.54);font-size:1rem;line-height:18px;overflow:hidden;padding:16px;width:90%}.mdl-card__actions{font-size:16px;line-height:normal;width:100%;background-color:transparent;padding:8px;box-sizing:border-box}.mdl-card__actions.mdl-card--border{border-top:1px solid rgba(0,0,0,.1)}.mdl-card--expand{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-card__menu{position:absolute;right:16px;top:16px}.mdl-checkbox{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0}.mdl-checkbox.is-upgraded{padding-left:24px}.mdl-checkbox__input{line-height:24px}.mdl-checkbox.is-upgraded .mdl-checkbox__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-checkbox__box-outline{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;overflow:hidden;border:2px solid rgba(0,0,0,.54);border-radius:2px;z-index:2}.mdl-checkbox.is-checked .mdl-checkbox__box-outline{border:2px solid rgb(121,85,72)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__box-outline,.mdl-checkbox.is-disabled .mdl-checkbox__box-outline{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__focus-helper{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:transparent}.mdl-checkbox.is-focused .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-checkbox.is-focused.is-checked .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(121,85,72,.26);background-color:rgba(121,85,72,.26)}.mdl-checkbox__tick-outline{position:absolute;top:0;left:0;height:100%;width:100%;-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");background:0 0;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background}.mdl-checkbox.is-checked .mdl-checkbox__tick-outline{background:rgb(121,85,72)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}fieldset[disabled] .mdl-checkbox.is-checked .mdl-checkbox__tick-outline,.mdl-checkbox.is-checked.is-disabled .mdl-checkbox__tick-outline{background:rgba(0,0,0,.26)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}.mdl-checkbox__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0}fieldset[disabled] .mdl-checkbox .mdl-checkbox__label,.mdl-checkbox.is-disabled .mdl-checkbox__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__ripple-container{position:absolute;z-index:2;top:-6px;left:-10px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-checkbox__ripple-container .mdl-ripple{background:rgb(121,85,72)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container{cursor:auto}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container .mdl-ripple,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container .mdl-ripple{background:0 0}.mdl-chip{height:32px;font-family:"Roboto","Helvetica","Arial",sans-serif;line-height:32px;padding:0 12px;border:0;border-radius:16px;background-color:#dedede;display:inline-block;color:rgba(0,0,0,.87);margin:2px 0;font-size:0;white-space:nowrap}.mdl-chip__text{font-size:13px;vertical-align:middle;display:inline-block}.mdl-chip__action{height:24px;width:24px;background:0 0;opacity:.54;cursor:pointer;padding:0;margin:0 0 0 4px;font-size:13px;text-decoration:none;color:rgba(0,0,0,.87);border:none;outline:none}.mdl-chip__action,.mdl-chip__contact{display:inline-block;vertical-align:middle;overflow:hidden;text-align:center}.mdl-chip__contact{height:32px;width:32px;border-radius:16px;margin-right:8px;font-size:18px;line-height:32px}.mdl-chip:focus{outline:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-chip:active{background-color:#d6d6d6}.mdl-chip--deletable{padding-right:4px}.mdl-chip--contact{padding-left:0}.mdl-data-table{position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff}.mdl-data-table thead{padding-bottom:3px}.mdl-data-table thead .mdl-data-table__select{margin-top:0}.mdl-data-table tbody tr{position:relative;height:48px;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background-color}.mdl-data-table tbody tr.is-selected{background-color:#e0e0e0}.mdl-data-table tbody tr:hover{background-color:#eee}.mdl-data-table td{text-align:right}.mdl-data-table th{padding:0 18px 12px 18px;text-align:right}.mdl-data-table td:first-of-type,.mdl-data-table th:first-of-type{padding-left:24px}.mdl-data-table td:last-of-type,.mdl-data-table th:last-of-type{padding-right:24px}.mdl-data-table td{position:relative;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px;box-sizing:border-box}.mdl-data-table td,.mdl-data-table td .mdl-data-table__select{vertical-align:middle}.mdl-data-table th{position:relative;vertical-align:bottom;text-overflow:ellipsis;font-weight:700;line-height:24px;letter-spacing:0;height:48px;font-size:12px;color:rgba(0,0,0,.54);padding-bottom:8px;box-sizing:border-box}.mdl-data-table th.mdl-data-table__header--sorted-ascending,.mdl-data-table th.mdl-data-table__header--sorted-descending{color:rgba(0,0,0,.87)}.mdl-data-table th.mdl-data-table__header--sorted-ascending:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:before{font-family:'Material Icons';font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased;font-size:16px;content:"\e5d8";margin-right:5px;vertical-align:sub}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover{cursor:pointer}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover:before{color:rgba(0,0,0,.26)}.mdl-data-table th.mdl-data-table__header--sorted-descending:before{content:"\e5db"}.mdl-data-table__select{width:16px}.mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric{text-align:left}.mdl-dialog{border:none;box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2);width:280px}.mdl-dialog__title{padding:24px 24px 0;margin:0;font-size:2.5rem}.mdl-dialog__actions{padding:8px 8px 8px 24px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.mdl-dialog__actions>*{margin-right:8px;height:36px}.mdl-dialog__actions>*:first-child{margin-right:0}.mdl-dialog__actions--full-width{padding:0 0 8px}.mdl-dialog__actions--full-width>*{height:48px;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;padding-right:16px;margin-right:0;text-align:right}.mdl-dialog__content{padding:20px 24px 24px;color:rgba(0,0,0,.54)}.mdl-mega-footer{padding:16px 40px;color:#9e9e9e;background-color:#424242}.mdl-mega-footer--top-section:after,.mdl-mega-footer--middle-section:after,.mdl-mega-footer--bottom-section:after,.mdl-mega-footer__top-section:after,.mdl-mega-footer__middle-section:after,.mdl-mega-footer__bottom-section:after{content:'';display:block;clear:both}.mdl-mega-footer--left-section,.mdl-mega-footer__left-section,.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{margin-bottom:16px}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:block;margin-bottom:16px;color:inherit;text-decoration:none}@media screen and (min-width:760px){.mdl-mega-footer--left-section,.mdl-mega-footer__left-section{float:left}.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{float:right}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:inline-block;margin-left:16px;line-height:36px;vertical-align:middle}}.mdl-mega-footer--social-btn,.mdl-mega-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{display:block;position:relative}@media screen and (min-width:760px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{width:33%}.mdl-mega-footer--drop-down-section:nth-child(1),.mdl-mega-footer--drop-down-section:nth-child(2),.mdl-mega-footer__drop-down-section:nth-child(1),.mdl-mega-footer__drop-down-section:nth-child(2){float:left}.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(3){float:right}.mdl-mega-footer--drop-down-section:nth-child(3):after,.mdl-mega-footer__drop-down-section:nth-child(3):after{clear:right}.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section:nth-child(4){clear:right;float:right}.mdl-mega-footer--middle-section:after,.mdl-mega-footer__middle-section:after{content:'';display:block;clear:both}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:0}}@media screen and (min-width:1024px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section,.mdl-mega-footer__drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(4){width:24%;float:left}}.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{position:absolute;width:100%;height:55.8px;padding:32px;margin:-16px 0 0;cursor:pointer;z-index:1;opacity:0}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CE'}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list{display:none}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CF'}.mdl-mega-footer--heading,.mdl-mega-footer__heading{position:relative;width:100%;padding-right:39.8px;margin-bottom:16px;box-sizing:border-box;font-size:14px;line-height:23.8px;font-weight:500;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#e0e0e0}.mdl-mega-footer--heading:after,.mdl-mega-footer__heading:after{content:'';position:absolute;top:0;right:0;display:block;width:23.8px;height:23.8px;background-size:cover}.mdl-mega-footer--link-list,.mdl-mega-footer__link-list{list-style:none;padding:0;margin:0 0 32px}.mdl-mega-footer--link-list:after,.mdl-mega-footer__link-list:after{clear:both;display:block;content:''}.mdl-mega-footer--link-list li,.mdl-mega-footer__link-list li{font-size:14px;font-weight:400;letter-spacing:0;line-height:20px}.mdl-mega-footer--link-list a,.mdl-mega-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}@media screen and (min-width:760px){.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{display:none}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{content:''}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list{display:block}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{content:''}}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:16px;margin-bottom:16px}.mdl-logo{margin-bottom:16px;color:#fff}.mdl-mega-footer--bottom-section .mdl-mega-footer--link-list li,.mdl-mega-footer__bottom-section .mdl-mega-footer__link-list li{float:left;margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-logo{float:left;margin-bottom:0;margin-right:16px}}.mdl-mini-footer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:32px 16px;color:#9e9e9e;background-color:#424242}.mdl-mini-footer:after{content:'';display:block}.mdl-mini-footer .mdl-logo{line-height:36px}.mdl-mini-footer--link-list,.mdl-mini-footer__link-list{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;list-style:none;margin:0;padding:0}.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{line-height:36px}}.mdl-mini-footer--link-list a,.mdl-mini-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}.mdl-mini-footer--left-section,.mdl-mini-footer__left-section{display:inline-block;-webkit-order:0;-ms-flex-order:0;order:0}.mdl-mini-footer--right-section,.mdl-mini-footer__right-section{display:inline-block;-webkit-order:1;-ms-flex-order:1;order:1}.mdl-mini-footer--social-btn,.mdl-mini-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-icon-toggle{position:relative;z-index:1;vertical-align:middle;display:inline-block;height:32px;margin:0;padding:0}.mdl-icon-toggle__input{line-height:32px}.mdl-icon-toggle.is-upgraded .mdl-icon-toggle__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-icon-toggle__label{display:inline-block;position:relative;cursor:pointer;height:32px;width:32px;min-width:32px;color:#616161;border-radius:50%;padding:0;margin-left:0;margin-right:0;text-align:center;background-color:transparent;will-change:background-color;transition:background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1)}.mdl-icon-toggle__label.material-icons{line-height:32px;font-size:24px}.mdl-icon-toggle.is-checked .mdl-icon-toggle__label{color:rgb(121,85,72)}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__label{color:rgba(0,0,0,.26);cursor:auto;transition:none}.mdl-icon-toggle.is-focused .mdl-icon-toggle__label{background-color:rgba(0,0,0,.12)}.mdl-icon-toggle.is-focused.is-checked .mdl-icon-toggle__label{background-color:rgba(121,85,72,.26)}.mdl-icon-toggle__ripple-container{position:absolute;z-index:2;top:-2px;left:-2px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-icon-toggle__ripple-container .mdl-ripple{background:#616161}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container{cursor:auto}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container .mdl-ripple{background:0 0}.mdl-list{display:block;padding:8px 0;list-style:none}.mdl-list__item{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:.04em;line-height:1;min-height:48px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;padding:16px;cursor:default;color:rgba(0,0,0,.87);overflow:hidden}.mdl-list__item,.mdl-list__item .mdl-list__item-primary-content{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.mdl-list__item .mdl-list__item-primary-content{-webkit-order:0;-ms-flex-order:0;order:0;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2;text-decoration:none}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-icon{margin-right:32px}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-avatar{margin-right:16px}.mdl-list__item .mdl-list__item-secondary-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column;-ms-flex-flow:column;flex-flow:column;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;margin-left:16px}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-action label{display:inline}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-info{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;color:rgba(0,0,0,.54)}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-sub-header{padding:0 0 0 16px}.mdl-list__item-icon,.mdl-list__item-icon.material-icons{height:24px;width:24px;font-size:24px;box-sizing:border-box;color:#757575}.mdl-list__item-avatar,.mdl-list__item-avatar.material-icons{height:40px;width:40px;box-sizing:border-box;border-radius:50%;background-color:#757575;font-size:40px;color:#fff}.mdl-list__item--two-line{height:72px}.mdl-list__item--two-line .mdl-list__item-primary-content{height:36px;line-height:20px;display:block}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-avatar{float:left}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left;margin-top:6px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-secondary-content{height:36px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-sub-title{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-list__item--three-line{height:88px}.mdl-list__item--three-line .mdl-list__item-primary-content{height:52px;line-height:20px;display:block}.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-avatar,.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left}.mdl-list__item--three-line .mdl-list__item-secondary-content{height:52px}.mdl-list__item--three-line .mdl-list__item-text-body{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;height:52px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-menu__container{display:block;margin:0;padding:0;border:none;position:absolute;overflow:visible;height:0;width:0;visibility:hidden;z-index:-1}.mdl-menu__container.is-visible,.mdl-menu__container.is-animating{z-index:999;visibility:visible}.mdl-menu__outline{display:block;background:#fff;margin:0;padding:0;border:none;border-radius:2px;position:absolute;top:0;left:0;overflow:hidden;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;transform-origin:0 0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);will-change:transform;transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1);z-index:-1}.mdl-menu__container.is-visible .mdl-menu__outline{opacity:1;-webkit-transform:scale(1);transform:scale(1);z-index:999}.mdl-menu__outline.mdl-menu--bottom-right{-webkit-transform-origin:100% 0;transform-origin:100% 0}.mdl-menu__outline.mdl-menu--top-left{-webkit-transform-origin:0 100%;transform-origin:0 100%}.mdl-menu__outline.mdl-menu--top-right{-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.mdl-menu{position:absolute;list-style:none;top:0;left:0;height:auto;width:auto;min-width:124px;padding:8px 0;margin:0;opacity:0;clip:rect(0 0 0 0);z-index:-1}.mdl-menu__container.is-visible .mdl-menu{opacity:1;z-index:999}.mdl-menu.is-animating{transition:opacity .2s cubic-bezier(.4,0,.2,1),clip .3s cubic-bezier(.4,0,.2,1)}.mdl-menu.mdl-menu--bottom-right{left:auto;right:0}.mdl-menu.mdl-menu--top-left{top:auto;bottom:0}.mdl-menu.mdl-menu--top-right{top:auto;left:auto;bottom:0;right:0}.mdl-menu.mdl-menu--unaligned{top:auto;left:auto}.mdl-menu__item{display:block;border:none;color:rgba(0,0,0,.87);background-color:transparent;text-align:left;margin:0;padding:0 16px;outline-color:#bdbdbd;position:relative;overflow:hidden;font-size:14px;font-weight:400;letter-spacing:0;text-decoration:none;cursor:pointer;height:48px;line-height:48px;white-space:nowrap;opacity:0;transition:opacity .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-menu__container.is-visible .mdl-menu__item{opacity:1}.mdl-menu__item::-moz-focus-inner{border:0}.mdl-menu__item--full-bleed-divider{border-bottom:1px solid rgba(0,0,0,.12)}.mdl-menu__item[disabled],.mdl-menu__item[data-mdl-disabled]{color:#bdbdbd;background-color:transparent;cursor:auto}.mdl-menu__item[disabled]:hover,.mdl-menu__item[data-mdl-disabled]:hover{background-color:transparent}.mdl-menu__item[disabled]:focus,.mdl-menu__item[data-mdl-disabled]:focus{background-color:transparent}.mdl-menu__item[disabled] .mdl-ripple,.mdl-menu__item[data-mdl-disabled] .mdl-ripple{background:0 0}.mdl-menu__item:hover{background-color:#eee}.mdl-menu__item:focus{outline:none;background-color:#eee}.mdl-menu__item:active{background-color:#e0e0e0}.mdl-menu__item--ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-progress{display:block;position:relative;height:4px;width:500px;max-width:100%}.mdl-progress>.bar{display:block;position:absolute;top:0;bottom:0;width:0%;transition:width .2s cubic-bezier(.4,0,.2,1)}.mdl-progress>.progressbar{background-color:rgb(121,85,72);z-index:1;left:0}.mdl-progress>.bufferbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(121,85,72),rgb(121,85,72));z-index:0;left:0}.mdl-progress>.auxbar{right:0}@supports (-webkit-appearance:none){.mdl-progress:not(.mdl-progress--indeterminate):not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate):not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(121,85,72),rgb(121,85,72));-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=")}}.mdl-progress:not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.9),rgba(255,255,255,.9)),linear-gradient(to right,rgb(121,85,72),rgb(121,85,72))}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1{-webkit-animation-name:indeterminate1;animation-name:indeterminate1}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1,.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-color:rgb(121,85,72);-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-image:none;-webkit-animation-name:indeterminate2;animation-name:indeterminate2}@-webkit-keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@-webkit-keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}@keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}.mdl-navigation{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;box-sizing:border-box}.mdl-navigation__link{color:#424242;text-decoration:none;margin:0;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0;opacity:.87}.mdl-navigation__link .material-icons{vertical-align:middle}.mdl-layout{width:100%;height:100%;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow-y:auto;overflow-x:hidden;position:relative;-webkit-overflow-scrolling:touch}.mdl-layout.is-small-screen .mdl-layout--large-screen-only{display:none}.mdl-layout:not(.is-small-screen) .mdl-layout--small-screen-only{display:none}.mdl-layout__container{position:absolute;width:100%;height:100%}.mdl-layout__title,.mdl-layout-title{display:block;position:relative;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400;box-sizing:border-box}.mdl-layout-spacer{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-layout__drawer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:240px;height:100%;max-height:100%;position:absolute;top:0;left:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);box-sizing:border-box;border-right:1px solid #e0e0e0;background:#fafafa;-webkit-transform:translateX(-250px);transform:translateX(-250px);-webkit-transform-style:preserve-3d;transform-style:preserve-3d;will-change:transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;color:#424242;overflow:visible;overflow-y:auto;z-index:5}.mdl-layout__drawer.is-visible{-webkit-transform:translateX(0);transform:translateX(0)}.mdl-layout__drawer.is-visible~.mdl-layout__content.mdl-layout__content{overflow:hidden}.mdl-layout__drawer>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:64px;padding-left:40px}@media screen and (max-width:1024px){.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:56px;padding-left:16px}}.mdl-layout__drawer .mdl-navigation{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-ms-flex-align:stretch;-ms-grid-row-align:stretch;align-items:stretch;padding-top:16px}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{display:block;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;padding:16px 40px;margin:0;color:#757575}@media screen and (max-width:1024px){.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{padding:16px}}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link:hover{background-color:#e0e0e0}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link--current{background-color:#e0e0e0;color:#000}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__drawer{-webkit-transform:translateX(0);transform:translateX(0)}}.mdl-layout__drawer-button{display:block;position:absolute;height:48px;width:48px;border:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden;text-align:center;cursor:pointer;font-size:26px;line-height:56px;font-family:Helvetica,Arial,sans-serif;margin:8px 12px;top:0;left:0;color:rgb(255,255,255);z-index:4}.mdl-layout__header .mdl-layout__drawer-button{position:absolute;color:rgb(255,255,255);background-color:inherit}@media screen and (max-width:1024px){.mdl-layout__header .mdl-layout__drawer-button{margin:4px}}@media screen and (max-width:1024px){.mdl-layout__drawer-button{margin:4px;color:rgba(0,0,0,.5)}}@media screen and (min-width:1025px){.mdl-layout__drawer-button{line-height:54px}.mdl-layout--no-desktop-drawer-button .mdl-layout__drawer-button,.mdl-layout--fixed-drawer>.mdl-layout__drawer-button,.mdl-layout--no-drawer-button .mdl-layout__drawer-button{display:none}}.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;box-sizing:border-box;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;width:100%;margin:0;padding:0;border:none;min-height:64px;max-height:1000px;z-index:3;background-color:rgb(121,85,72);color:rgb(255,255,255);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:max-height,box-shadow}@media screen and (max-width:1024px){.mdl-layout__header{min-height:56px}}.mdl-layout--fixed-drawer.is-upgraded:not(.is-small-screen)>.mdl-layout__header{margin-left:240px;width:calc(100% - 240px)}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__header .mdl-layout__header-row{padding-left:40px}}.mdl-layout__header>.mdl-layout-icon{position:absolute;left:40px;top:16px;height:32px;width:32px;overflow:hidden;z-index:3;display:block}@media screen and (max-width:1024px){.mdl-layout__header>.mdl-layout-icon{left:16px;top:12px}}.mdl-layout.has-drawer .mdl-layout__header>.mdl-layout-icon{display:none}.mdl-layout__header.is-compact{max-height:64px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact{max-height:56px}}.mdl-layout__header.is-compact.has-tabs{height:112px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact.has-tabs{min-height:104px}}@media screen and (max-width:1024px){.mdl-layout__header{display:none}.mdl-layout--fixed-header>.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex}}.mdl-layout__header--transparent.mdl-layout__header--transparent{background-color:transparent;box-shadow:none}.mdl-layout__header--seamed,.mdl-layout__header--scroll{box-shadow:none}.mdl-layout__header--waterfall{box-shadow:none;overflow:hidden}.mdl-layout__header--waterfall.is-casting-shadow{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-layout__header--waterfall.mdl-layout__header--waterfall-hide-top{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.mdl-layout__header-row{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:64px;margin:0;padding:0 40px 0 80px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:40px}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__header-row{padding-left:40px}}@media screen and (max-width:1024px){.mdl-layout__header-row{height:56px;padding:0 16px 0 72px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:16px}}.mdl-layout__header-row>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__header--scroll .mdl-layout__header-row{width:100%}.mdl-layout__header-row .mdl-navigation{margin:0;padding:0;height:64px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation{height:56px}}.mdl-layout__header-row .mdl-navigation__link{display:block;color:rgb(255,255,255);line-height:64px;padding:0 24px}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation__link{line-height:56px;padding:0 16px}}.mdl-layout__obfuscator{background-color:transparent;position:absolute;top:0;left:0;height:100%;width:100%;z-index:4;visibility:hidden;transition-property:background-color;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-layout__obfuscator.is-visible{background-color:rgba(0,0,0,.5);visibility:visible}@supports (pointer-events:auto){.mdl-layout__obfuscator{background-color:rgba(0,0,0,.5);opacity:0;transition-property:opacity;visibility:visible;pointer-events:none}.mdl-layout__obfuscator.is-visible{pointer-events:auto;opacity:1}}.mdl-layout__content{-ms-flex:0 1 auto;position:relative;display:inline-block;overflow-y:auto;overflow-x:hidden;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;z-index:1;-webkit-overflow-scrolling:touch}.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:240px}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow:visible}@media screen and (max-width:1024px){.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:0}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow-y:auto;overflow-x:hidden}}.mdl-layout__tab-bar{height:96px;margin:0;width:calc(100% - 112px);padding:0 0 0 56px;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:rgb(121,85,72);overflow-y:hidden;overflow-x:scroll}.mdl-layout__tab-bar::-webkit-scrollbar{display:none}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}}@media screen and (max-width:1024px){.mdl-layout__tab-bar{width:calc(100% - 60px);padding:0 0 0 60px}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{width:calc(100% - 8px);padding-left:4px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar{padding:0;overflow:hidden;width:100%}.mdl-layout__tab-bar-container{position:relative;height:48px;width:100%;border:none;margin:0;z-index:2;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden}.mdl-layout__container>.mdl-layout__tab-bar-container{position:absolute;top:0;left:0}.mdl-layout__tab-bar-button{display:inline-block;position:absolute;top:0;height:48px;width:56px;z-index:4;text-align:center;background-color:rgb(121,85,72);color:transparent;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button{width:16px}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button .material-icons,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button .material-icons{position:relative;left:-4px}@media screen and (max-width:1024px){.mdl-layout__tab-bar-button{width:60px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar-button{display:none}.mdl-layout__tab-bar-button .material-icons{line-height:48px}.mdl-layout__tab-bar-button.is-active{color:rgb(255,255,255)}.mdl-layout__tab-bar-left-button{left:0}.mdl-layout__tab-bar-right-button{right:0}.mdl-layout__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(255,255,255,.6);overflow:hidden}@media screen and (max-width:1024px){.mdl-layout__tab{padding:0 12px}}.mdl-layout--fixed-tabs .mdl-layout__tab{float:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;padding:0}.mdl-layout.is-upgraded .mdl-layout__tab.is-active{color:rgb(255,255,255)}.mdl-layout.is-upgraded .mdl-layout__tab.is-active::after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(255,171,64);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-layout__tab .mdl-layout__tab-ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-layout__tab .mdl-layout__tab-ripple-container .mdl-ripple{background-color:rgb(255,255,255)}.mdl-layout__tab-panel{display:block}.mdl-layout.is-upgraded .mdl-layout__tab-panel{display:none}.mdl-layout.is-upgraded .mdl-layout__tab-panel.is-active{display:block}.mdl-radio{position:relative;font-size:16px;line-height:24px;display:inline-block;box-sizing:border-box;margin:0;padding-left:0}.mdl-radio.is-upgraded{padding-left:24px}.mdl-radio__button{line-height:24px}.mdl-radio.is-upgraded .mdl-radio__button{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-radio__outer-circle{position:absolute;top:4px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;border:2px solid rgba(0,0,0,.54);border-radius:50%;z-index:2}.mdl-radio.is-checked .mdl-radio__outer-circle{border:2px solid rgb(121,85,72)}.mdl-radio__outer-circle fieldset[disabled] .mdl-radio,.mdl-radio.is-disabled .mdl-radio__outer-circle{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-radio__inner-circle{position:absolute;z-index:1;margin:0;top:8px;left:4px;box-sizing:border-box;width:8px;height:8px;cursor:pointer;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;-webkit-transform:scale3d(0,0,0);transform:scale3d(0,0,0);border-radius:50%;background:rgb(121,85,72)}.mdl-radio.is-checked .mdl-radio__inner-circle{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}fieldset[disabled] .mdl-radio .mdl-radio__inner-circle,.mdl-radio.is-disabled .mdl-radio__inner-circle{background:rgba(0,0,0,.26);cursor:auto}.mdl-radio.is-focused .mdl-radio__inner-circle{box-shadow:0 0 0 10px rgba(0,0,0,.1)}.mdl-radio__label{cursor:pointer}fieldset[disabled] .mdl-radio .mdl-radio__label,.mdl-radio.is-disabled .mdl-radio__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-radio__ripple-container{position:absolute;z-index:2;top:-9px;left:-13px;box-sizing:border-box;width:42px;height:42px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-radio__ripple-container .mdl-ripple{background:rgb(121,85,72)}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container,.mdl-radio.is-disabled .mdl-radio__ripple-container{cursor:auto}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container .mdl-ripple,.mdl-radio.is-disabled .mdl-radio__ripple-container .mdl-ripple{background:0 0}_:-ms-input-placeholder,:root .mdl-slider.mdl-slider.is-upgraded{-ms-appearance:none;height:32px;margin:0}.mdl-slider{width:calc(100% - 40px);margin:0 20px}.mdl-slider.is-upgraded{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:2px;background:0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:0;padding:0;color:rgb(121,85,72);-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;z-index:1;cursor:pointer}.mdl-slider.is-upgraded::-moz-focus-outer{border:0}.mdl-slider.is-upgraded::-ms-tooltip{display:none}.mdl-slider.is-upgraded::-webkit-slider-runnable-track{background:0 0}.mdl-slider.is-upgraded::-moz-range-track{background:0 0;border:none}.mdl-slider.is-upgraded::-ms-track{background:0 0;color:transparent;height:2px;width:100%;border:none}.mdl-slider.is-upgraded::-ms-fill-lower{padding:0;background:linear-gradient(to right,transparent,transparent 16px,rgb(121,85,72)16px,rgb(121,85,72)0)}.mdl-slider.is-upgraded::-ms-fill-upper{padding:0;background:linear-gradient(to left,transparent,transparent 16px,rgba(0,0,0,.26)16px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background:rgb(121,85,72);border:none;transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded::-moz-range-thumb{-moz-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background-image:none;background:rgb(121,85,72);border:none}.mdl-slider.is-upgraded:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(121,85,72,.26)}.mdl-slider.is-upgraded:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(121,85,72,.26)}.mdl-slider.is-upgraded:active::-webkit-slider-thumb{background-image:none;background:rgb(121,85,72);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded:active::-moz-range-thumb{background-image:none;background:rgb(121,85,72);transform:scale(1.5)}.mdl-slider.is-upgraded::-ms-thumb{width:32px;height:32px;border:none;border-radius:50%;background:rgb(121,85,72);transform:scale(.375);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgb(121,85,72)0%,rgb(121,85,72)37.5%,rgba(121,85,72,.26)37.5%,rgba(121,85,72,.26)100%);transform:scale(1)}.mdl-slider.is-upgraded:active::-ms-thumb{background:rgb(121,85,72);transform:scale(.5625)}.mdl-slider.is-upgraded.is-lowest-value::-webkit-slider-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-moz-range-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:active::-webkit-slider-thumb{border:1.6px solid rgba(0,0,0,.26);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:9px}.mdl-slider.is-upgraded.is-lowest-value:active::-moz-range-thumb{border:1.5px solid rgba(0,0,0,.26);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value::-ms-thumb{background:radial-gradient(circle closest-side,transparent 0%,transparent 66.67%,rgba(0,0,0,.26)66.67%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgba(0,0,0,.12)0%,rgba(0,0,0,.12)25%,rgba(0,0,0,.26)25%,rgba(0,0,0,.26)37.5%,rgba(0,0,0,.12)37.5%,rgba(0,0,0,.12)100%);transform:scale(1)}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-thumb{transform:scale(.5625);background:radial-gradient(circle closest-side,transparent 0%,transparent 77.78%,rgba(0,0,0,.26)77.78%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-lower{background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-fill-upper{margin-left:9px}.mdl-slider.is-upgraded:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled::-webkit-slider-thumb{-webkit-transform:scale(.667);transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded:disabled::-moz-range-thumb{transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-lower{background-color:rgba(0,0,0,.26);left:-6px}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-webkit-slider-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;-webkit-transform:scale(.667);transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-moz-range-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded:disabled:active::-ms-thumb,.mdl-slider.is-upgraded:disabled::-ms-thumb{transform:scale(.25);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-ms-thumb{transform:scale(.25);background:radial-gradient(circle closest-side,transparent 0%,transparent 50%,rgba(0,0,0,.26)50%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded:disabled::-ms-fill-lower{margin-right:6px;background:linear-gradient(to right,transparent,transparent 25px,rgba(0,0,0,.26)25px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded:disabled::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-fill-upper{margin-left:6px}.mdl-slider__ie-container{height:18px;overflow:visible;border:none;margin:none;padding:none}.mdl-slider__container{height:18px;position:relative;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.mdl-slider__container,.mdl-slider__background-flex{background:0 0;display:-webkit-flex;display:-ms-flexbox;display:flex}.mdl-slider__background-flex{position:absolute;height:2px;width:calc(100% - 52px);top:50%;left:0;margin:0 26px;overflow:hidden;border:0;padding:0;-webkit-transform:translate(0,-1px);transform:translate(0,-1px)}.mdl-slider__background-lower{background:rgb(121,85,72)}.mdl-slider__background-lower,.mdl-slider__background-upper{-webkit-flex:0;-ms-flex:0;flex:0;position:relative;border:0;padding:0}.mdl-slider__background-upper{background:rgba(0,0,0,.26);transition:left .18s cubic-bezier(.4,0,.2,1)}.mdl-snackbar{position:fixed;bottom:0;left:50%;cursor:default;background-color:#323232;z-index:3;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:"Roboto","Helvetica","Arial",sans-serif;will-change:transform;-webkit-transform:translate(0,80px);transform:translate(0,80px);transition:transform .25s cubic-bezier(.4,0,1,1);transition:transform .25s cubic-bezier(.4,0,1,1),-webkit-transform .25s cubic-bezier(.4,0,1,1);pointer-events:none}@media (max-width:479px){.mdl-snackbar{width:100%;left:0;min-height:48px;max-height:80px}}@media (min-width:480px){.mdl-snackbar{min-width:288px;max-width:568px;border-radius:2px;-webkit-transform:translate(-50%,80px);transform:translate(-50%,80px)}}.mdl-snackbar--active{-webkit-transform:translate(0,0);transform:translate(0,0);pointer-events:auto;transition:transform .25s cubic-bezier(0,0,.2,1);transition:transform .25s cubic-bezier(0,0,.2,1),-webkit-transform .25s cubic-bezier(0,0,.2,1)}@media (min-width:480px){.mdl-snackbar--active{-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}}.mdl-snackbar__text{padding:14px 12px 14px 24px;vertical-align:middle;color:#fff;float:left}.mdl-snackbar__action{background:0 0;border:none;color:rgb(255,171,64);float:right;padding:14px 24px 14px 12px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;line-height:1;letter-spacing:0;overflow:hidden;outline:none;opacity:0;pointer-events:none;cursor:pointer;text-decoration:none;text-align:center;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-snackbar__action::-moz-focus-inner{border:0}.mdl-snackbar__action:not([aria-hidden]){opacity:1;pointer-events:auto}.mdl-spinner{display:inline-block;position:relative;width:28px;height:28px}.mdl-spinner:not(.is-upgraded).is-active:after{content:"Loading..."}.mdl-spinner.is-upgraded.is-active{-webkit-animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite;animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite}@-webkit-keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.mdl-spinner__layer{position:absolute;width:100%;height:100%;opacity:0}.mdl-spinner__layer-1{border-color:#42a5f5}.mdl-spinner--single-color .mdl-spinner__layer-1{border-color:rgb(121,85,72)}.mdl-spinner.is-active .mdl-spinner__layer-1{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-2{border-color:#f44336}.mdl-spinner--single-color .mdl-spinner__layer-2{border-color:rgb(121,85,72)}.mdl-spinner.is-active .mdl-spinner__layer-2{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-3{border-color:#fdd835}.mdl-spinner--single-color .mdl-spinner__layer-3{border-color:rgb(121,85,72)}.mdl-spinner.is-active .mdl-spinner__layer-3{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-4{border-color:#4caf50}.mdl-spinner--single-color .mdl-spinner__layer-4{border-color:rgb(121,85,72)}.mdl-spinner.is-active .mdl-spinner__layer-4{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@-webkit-keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}@keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}.mdl-spinner__gap-patch{position:absolute;box-sizing:border-box;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__gap-patch .mdl-spinner__circle{width:1000%;left:-450%}.mdl-spinner__circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__circle-clipper .mdl-spinner__circle{width:200%}.mdl-spinner__circle{box-sizing:border-box;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent!important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0;left:0}.mdl-spinner__left .mdl-spinner__circle{border-right-color:transparent!important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.mdl-spinner.is-active .mdl-spinner__left .mdl-spinner__circle{-webkit-animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__right .mdl-spinner__circle{left:-100%;border-left-color:transparent!important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.mdl-spinner.is-active .mdl-spinner__right .mdl-spinner__circle{-webkit-animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}@keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}.mdl-switch{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0;overflow:visible;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-switch.is-upgraded{padding-left:28px}.mdl-switch__input{line-height:24px}.mdl-switch.is-upgraded .mdl-switch__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-switch__track{background:rgba(0,0,0,.26);position:absolute;left:0;top:5px;height:14px;width:36px;border-radius:14px;cursor:pointer}.mdl-switch.is-checked .mdl-switch__track{background:rgba(121,85,72,.5)}.mdl-switch__track fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__track{background:rgba(0,0,0,.12);cursor:auto}.mdl-switch__thumb{background:#fafafa;position:absolute;left:0;top:2px;height:20px;width:20px;border-radius:50%;cursor:pointer;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:left}.mdl-switch.is-checked .mdl-switch__thumb{background:rgb(121,85,72);left:16px;box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-switch__thumb fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__thumb{background:#bdbdbd;cursor:auto}.mdl-switch__focus-helper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-4px,-4px);transform:translate(-4px,-4px);display:inline-block;box-sizing:border-box;width:8px;height:8px;border-radius:50%;background-color:transparent}.mdl-switch.is-focused .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-switch.is-focused.is-checked .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(121,85,72,.26);background-color:rgba(121,85,72,.26)}.mdl-switch__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0;left:24px}.mdl-switch__label fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__label{color:#bdbdbd;cursor:auto}.mdl-switch__ripple-container{position:absolute;z-index:2;top:-12px;left:-14px;box-sizing:border-box;width:48px;height:48px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000);transition-duration:.4s;transition-timing-function:step-end;transition-property:left}.mdl-switch__ripple-container .mdl-ripple{background:rgb(121,85,72)}.mdl-switch__ripple-container fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__ripple-container{cursor:auto}fieldset[disabled] .mdl-switch .mdl-switch__ripple-container .mdl-ripple,.mdl-switch.is-disabled .mdl-switch__ripple-container .mdl-ripple{background:0 0}.mdl-switch.is-checked .mdl-switch__ripple-container{left:2px}.mdl-tabs{display:block;width:100%}.mdl-tabs__tab-bar{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:space-between;-ms-flex-line-pack:justify;align-content:space-between;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;height:48px;padding:0;margin:0;border-bottom:1px solid #e0e0e0}.mdl-tabs__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(0,0,0,.54);overflow:hidden}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active{color:rgba(0,0,0,.87)}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active:after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(121,85,72);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-tabs__tab .mdl-tabs__ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-tabs__tab .mdl-tabs__ripple-container .mdl-ripple{background:rgb(121,85,72)}.mdl-tabs__panel{display:block}.mdl-tabs.is-upgraded .mdl-tabs__panel{display:none}.mdl-tabs.is-upgraded .mdl-tabs__panel.is-active{display:block}@-webkit-keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}@keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}.mdl-textfield{position:relative;font-size:16px;display:inline-block;box-sizing:border-box;width:300px;max-width:100%;margin:0;padding:20px 0}.mdl-textfield .mdl-button{position:absolute;bottom:20px}.mdl-textfield--align-right{text-align:right}.mdl-textfield--full-width{width:100%}.mdl-textfield--expandable{min-width:32px;width:auto;min-height:32px}.mdl-textfield--expandable .mdl-button--icon{top:16px}.mdl-textfield__input{border:none;border-bottom:1px solid rgba(0,0,0,.12);display:block;font-size:16px;font-family:"Helvetica","Arial",sans-serif;margin:0;padding:4px 0;width:100%;background:0 0;text-align:left;color:inherit}.mdl-textfield__input[type="number"]{-moz-appearance:textfield}.mdl-textfield__input[type="number"]::-webkit-inner-spin-button,.mdl-textfield__input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.mdl-textfield.is-focused .mdl-textfield__input{outline:none}.mdl-textfield.is-invalid .mdl-textfield__input{border-color:#d50000;box-shadow:none}fieldset[disabled] .mdl-textfield .mdl-textfield__input,.mdl-textfield.is-disabled .mdl-textfield__input{background-color:transparent;border-bottom:1px dotted rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-textfield textarea.mdl-textfield__input{display:block}.mdl-textfield__label{bottom:0;color:rgba(0,0,0,.26);font-size:16px;left:0;right:0;pointer-events:none;position:absolute;display:block;top:24px;width:100%;overflow:hidden;white-space:nowrap;text-align:left}.mdl-textfield.is-dirty .mdl-textfield__label,.mdl-textfield.has-placeholder .mdl-textfield__label{visibility:hidden}.mdl-textfield--floating-label .mdl-textfield__label{transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{transition:none}fieldset[disabled] .mdl-textfield .mdl-textfield__label,.mdl-textfield.is-disabled.is-disabled .mdl-textfield__label{color:rgba(0,0,0,.26)}.mdl-textfield--floating-label.is-focused .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{color:rgb(121,85,72);font-size:12px;top:4px;visibility:visible}.mdl-textfield--floating-label.is-focused .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__expandable-holder .mdl-textfield__label{top:-16px}.mdl-textfield--floating-label.is-invalid .mdl-textfield__label{color:#d50000;font-size:12px}.mdl-textfield__label:after{background-color:rgb(121,85,72);bottom:20px;content:'';height:2px;left:45%;position:absolute;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);visibility:hidden;width:10px}.mdl-textfield.is-focused .mdl-textfield__label:after{left:0;visibility:visible;width:100%}.mdl-textfield.is-invalid .mdl-textfield__label:after{background-color:#d50000}.mdl-textfield__error{color:#d50000;position:absolute;font-size:12px;margin-top:3px;visibility:hidden;display:block}.mdl-textfield.is-invalid .mdl-textfield__error{visibility:visible}.mdl-textfield__expandable-holder{display:inline-block;position:relative;margin-left:32px;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-block;max-width:.1px}.mdl-textfield.is-focused .mdl-textfield__expandable-holder,.mdl-textfield.is-dirty .mdl-textfield__expandable-holder{max-width:600px}.mdl-textfield__expandable-holder .mdl-textfield__label:after{bottom:0}.mdl-tooltip{-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:top center;transform-origin:top center;z-index:999;background:rgba(97,97,97,.9);border-radius:2px;color:#fff;display:inline-block;font-size:10px;font-weight:500;line-height:14px;max-width:170px;position:fixed;top:-500px;left:-500px;padding:8px;text-align:center}.mdl-tooltip.is-active{-webkit-animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards;animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards}.mdl-tooltip--large{line-height:14px;font-size:14px;padding:16px}@-webkit-keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}@keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}.mdl-shadow--2dp{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-shadow--3dp{box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-shadow--4dp{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2)}.mdl-shadow--6dp{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.2)}.mdl-shadow--8dp{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.2)}.mdl-shadow--16dp{box-shadow:0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)}.mdl-shadow--24dp{box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2)}.mdl-grid{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;margin:0 auto;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.mdl-grid.mdl-grid--no-spacing{padding:0}.mdl-cell{box-sizing:border-box}.mdl-cell--top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.mdl-cell--middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-cell--bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.mdl-cell--stretch{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.mdl-grid.mdl-grid--no-spacing>.mdl-cell{margin:0}.mdl-cell--order-1{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12{-webkit-order:12;-ms-flex-order:12;order:12}@media (max-width:479px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:100%}.mdl-cell--hide-phone{display:none!important}.mdl-cell--order-1-phone.mdl-cell--order-1-phone{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-phone.mdl-cell--order-2-phone{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-phone.mdl-cell--order-3-phone{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-phone.mdl-cell--order-4-phone{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-phone.mdl-cell--order-5-phone{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-phone.mdl-cell--order-6-phone{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-phone.mdl-cell--order-7-phone{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-phone.mdl-cell--order-8-phone{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-phone.mdl-cell--order-9-phone{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-phone.mdl-cell--order-10-phone{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-phone.mdl-cell--order-11-phone{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-phone.mdl-cell--order-12-phone{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:25%}.mdl-cell--2-col,.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:50%}.mdl-cell--3-col,.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:75%}.mdl-cell--4-col,.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:100%}.mdl-cell--5-col,.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:100%}.mdl-cell--6-col,.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:100%}.mdl-cell--7-col,.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:100%}.mdl-cell--8-col,.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:25%}.mdl-cell--2-offset,.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:50%}.mdl-cell--3-offset,.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:75%}}@media (min-width:480px) and (max-width:839px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:50%}.mdl-cell--hide-tablet{display:none!important}.mdl-cell--order-1-tablet.mdl-cell--order-1-tablet{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-tablet.mdl-cell--order-2-tablet{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-tablet.mdl-cell--order-3-tablet{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-tablet.mdl-cell--order-4-tablet{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-tablet.mdl-cell--order-5-tablet{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-tablet.mdl-cell--order-6-tablet{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-tablet.mdl-cell--order-7-tablet{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-tablet.mdl-cell--order-8-tablet{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-tablet.mdl-cell--order-9-tablet{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-tablet.mdl-cell--order-10-tablet{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-tablet.mdl-cell--order-11-tablet{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-tablet.mdl-cell--order-12-tablet{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:calc(12.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:12.5%}.mdl-cell--2-col,.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:25%}.mdl-cell--3-col,.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:calc(37.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:37.5%}.mdl-cell--4-col,.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:50%}.mdl-cell--5-col,.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:calc(62.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:62.5%}.mdl-cell--6-col,.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:75%}.mdl-cell--7-col,.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:calc(87.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:87.5%}.mdl-cell--8-col,.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:calc(12.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:12.5%}.mdl-cell--2-offset,.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:25%}.mdl-cell--3-offset,.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:calc(37.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:37.5%}.mdl-cell--4-offset,.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:50%}.mdl-cell--5-offset,.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:calc(62.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:62.5%}.mdl-cell--6-offset,.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:75%}.mdl-cell--7-offset,.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:calc(87.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:87.5%}}@media (min-width:840px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:33.3333333333%}.mdl-cell--hide-desktop{display:none!important}.mdl-cell--order-1-desktop.mdl-cell--order-1-desktop{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-desktop.mdl-cell--order-2-desktop{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-desktop.mdl-cell--order-3-desktop{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-desktop.mdl-cell--order-4-desktop{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-desktop.mdl-cell--order-5-desktop{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-desktop.mdl-cell--order-6-desktop{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-desktop.mdl-cell--order-7-desktop{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-desktop.mdl-cell--order-8-desktop{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-desktop.mdl-cell--order-9-desktop{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-desktop.mdl-cell--order-10-desktop{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-desktop.mdl-cell--order-11-desktop{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-desktop.mdl-cell--order-12-desktop{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:calc(8.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:8.3333333333%}.mdl-cell--2-col,.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:calc(16.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:16.6666666667%}.mdl-cell--3-col,.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:25%}.mdl-cell--4-col,.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:33.3333333333%}.mdl-cell--5-col,.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:calc(41.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:41.6666666667%}.mdl-cell--6-col,.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:50%}.mdl-cell--7-col,.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:calc(58.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:58.3333333333%}.mdl-cell--8-col,.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:calc(66.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:66.6666666667%}.mdl-cell--9-col,.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:75%}.mdl-cell--10-col,.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:calc(83.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:83.3333333333%}.mdl-cell--11-col,.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:calc(91.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:91.6666666667%}.mdl-cell--12-col,.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:calc(8.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:8.3333333333%}.mdl-cell--2-offset,.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:calc(16.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:16.6666666667%}.mdl-cell--3-offset,.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:25%}.mdl-cell--4-offset,.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:calc(33.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:33.3333333333%}.mdl-cell--5-offset,.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:calc(41.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:41.6666666667%}.mdl-cell--6-offset,.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:50%}.mdl-cell--7-offset,.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:calc(58.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:58.3333333333%}.mdl-cell--8-offset,.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:calc(66.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:66.6666666667%}.mdl-cell--9-offset,.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:75%}.mdl-cell--10-offset,.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:calc(83.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:83.3333333333%}.mdl-cell--11-offset,.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:calc(91.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:91.6666666667%}}body{margin:0}.styleguide-demo h1{margin:48px 24px 0}.styleguide-demo h1:after{content:'';display:block;width:100%;border-bottom:1px solid rgba(0,0,0,.5);margin-top:24px}.styleguide-demo{opacity:0;transition:opacity .6s ease}.styleguide-masthead{height:256px;background:#212121;padding:115px 16px 0}.styleguide-container{position:relative;max-width:960px;width:100%}.styleguide-title{color:#fff;bottom:auto;position:relative;font-size:56px;font-weight:300;line-height:1;letter-spacing:-.02em}.styleguide-title:after{border-bottom:0}.styleguide-title span{font-weight:300}.mdl-styleguide .mdl-layout__drawer .mdl-navigation__link{padding:10px 24px}.demosLoaded .styleguide-demo{opacity:1}iframe{display:block;width:100%;border:none}iframe.heightSet{overflow:hidden}.demo-wrapper{margin:24px}.demo-wrapper iframe{border:1px solid rgba(0,0,0,.5)} \ No newline at end of file diff --git a/modules/material/www/material.indigo-purple.1.2.1.min.css b/modules/material/www/material.indigo-purple.1.2.1.min.css new file mode 100644 index 00000000..be4c43b2 --- /dev/null +++ b/modules/material/www/material.indigo-purple.1.2.1.min.css @@ -0,0 +1,8 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.2.1 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +@charset "UTF-8";html{color:rgba(0,0,0,.87)}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.browserupgrade{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.hidden{display:none!important}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}@media print{*,*:before,*:after,*:first-letter{background:transparent!important;color:#000!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}a,.mdl-accordion,.mdl-button,.mdl-card,.mdl-checkbox,.mdl-dropdown-menu,.mdl-icon-toggle,.mdl-item,.mdl-radio,.mdl-slider,.mdl-switch,.mdl-tabs__tab{-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:rgba(255,255,255,0)}html{width:100%;height:100%;-ms-touch-action:manipulation;touch-action:manipulation}body{width:100%;min-height:100%}main{display:block}*[hidden]{display:none!important}html,body{font-family:"Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:20px}h1,h2,h3,h4,h5,h6,p{padding:0}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400;line-height:1.35;letter-spacing:-.02em;opacity:.54;font-size:.6em}h1{font-size:56px;line-height:1.35;letter-spacing:-.02em;margin:24px 0}h1,h2{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h2{font-size:45px;line-height:48px}h2,h3{margin:24px 0}h3{font-size:34px;line-height:40px}h3,h4{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h4{font-size:24px;line-height:32px;-moz-osx-font-smoothing:grayscale;margin:24px 0 16px}h5{font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;margin:24px 0 16px}h6{font-size:16px;letter-spacing:.04em}h6,p{font-weight:400;line-height:24px}p{font-size:14px;letter-spacing:0;margin:0 0 16px}a{color:rgb(156,39,176);font-weight:500}blockquote{font-family:"Roboto","Helvetica","Arial",sans-serif;position:relative;font-size:24px;font-weight:300;font-style:italic;line-height:1.35;letter-spacing:.08em}blockquote:before{position:absolute;left:-.5em;content:'“'}blockquote:after{content:'”';margin-left:-.05em}mark{background-color:#f4ff81}dt{font-weight:700}address{font-size:12px;line-height:1;font-style:normal}address,ul,ol{font-weight:400;letter-spacing:0}ul,ol{font-size:14px;line-height:24px}.mdl-typography--display-4,.mdl-typography--display-4-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:112px;font-weight:300;line-height:1;letter-spacing:-.04em}.mdl-typography--display-4-color-contrast{opacity:.54}.mdl-typography--display-3,.mdl-typography--display-3-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:56px;font-weight:400;line-height:1.35;letter-spacing:-.02em}.mdl-typography--display-3-color-contrast{opacity:.54}.mdl-typography--display-2,.mdl-typography--display-2-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:45px;font-weight:400;line-height:48px}.mdl-typography--display-2-color-contrast{opacity:.54}.mdl-typography--display-1,.mdl-typography--display-1-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:34px;font-weight:400;line-height:40px}.mdl-typography--display-1-color-contrast{opacity:.54}.mdl-typography--headline,.mdl-typography--headline-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:24px;font-weight:400;line-height:32px;-moz-osx-font-smoothing:grayscale}.mdl-typography--headline-color-contrast{opacity:.87}.mdl-typography--title,.mdl-typography--title-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}.mdl-typography--title-color-contrast{opacity:.87}.mdl-typography--subhead,.mdl-typography--subhead-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;line-height:24px;letter-spacing:.04em}.mdl-typography--subhead-color-contrast{opacity:.87}.mdl-typography--body-2,.mdl-typography--body-2-color-contrast{font-size:14px;font-weight:700;line-height:24px;letter-spacing:0}.mdl-typography--body-2-color-contrast{opacity:.87}.mdl-typography--body-1,.mdl-typography--body-1-color-contrast{font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-color-contrast{opacity:.87}.mdl-typography--body-2-force-preferred-font,.mdl-typography--body-2-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:24px;letter-spacing:0}.mdl-typography--body-2-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--body-1-force-preferred-font,.mdl-typography--body-1-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--caption,.mdl-typography--caption-force-preferred-font{font-size:12px;font-weight:400;line-height:1;letter-spacing:0}.mdl-typography--caption-force-preferred-font{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--caption-color-contrast,.mdl-typography--caption-force-preferred-font-color-contrast{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;opacity:.54}.mdl-typography--caption-force-preferred-font-color-contrast,.mdl-typography--menu{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--menu{font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--menu-color-contrast{opacity:.87}.mdl-typography--menu-color-contrast,.mdl-typography--button,.mdl-typography--button-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--button,.mdl-typography--button-color-contrast{text-transform:uppercase}.mdl-typography--button-color-contrast{opacity:.87}.mdl-typography--text-left{text-align:left}.mdl-typography--text-right{text-align:right}.mdl-typography--text-center{text-align:center}.mdl-typography--text-justify{text-align:justify}.mdl-typography--text-nowrap{white-space:nowrap}.mdl-typography--text-lowercase{text-transform:lowercase}.mdl-typography--text-uppercase{text-transform:uppercase}.mdl-typography--text-capitalize{text-transform:capitalize}.mdl-typography--font-thin{font-weight:200!important}.mdl-typography--font-light{font-weight:300!important}.mdl-typography--font-regular{font-weight:400!important}.mdl-typography--font-medium{font-weight:500!important}.mdl-typography--font-bold{font-weight:700!important}.mdl-typography--font-black{font-weight:900!important}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased}.mdl-color-text--red{color:#f44336 !important}.mdl-color--red{background-color:#f44336 !important}.mdl-color-text--red-50{color:#ffebee !important}.mdl-color--red-50{background-color:#ffebee !important}.mdl-color-text--red-100{color:#ffcdd2 !important}.mdl-color--red-100{background-color:#ffcdd2 !important}.mdl-color-text--red-200{color:#ef9a9a !important}.mdl-color--red-200{background-color:#ef9a9a !important}.mdl-color-text--red-300{color:#e57373 !important}.mdl-color--red-300{background-color:#e57373 !important}.mdl-color-text--red-400{color:#ef5350 !important}.mdl-color--red-400{background-color:#ef5350 !important}.mdl-color-text--red-500{color:#f44336 !important}.mdl-color--red-500{background-color:#f44336 !important}.mdl-color-text--red-600{color:#e53935 !important}.mdl-color--red-600{background-color:#e53935 !important}.mdl-color-text--red-700{color:#d32f2f !important}.mdl-color--red-700{background-color:#d32f2f !important}.mdl-color-text--red-800{color:#c62828 !important}.mdl-color--red-800{background-color:#c62828 !important}.mdl-color-text--red-900{color:#b71c1c !important}.mdl-color--red-900{background-color:#b71c1c !important}.mdl-color-text--red-A100{color:#ff8a80 !important}.mdl-color--red-A100{background-color:#ff8a80 !important}.mdl-color-text--red-A200{color:#ff5252 !important}.mdl-color--red-A200{background-color:#ff5252 !important}.mdl-color-text--red-A400{color:#ff1744 !important}.mdl-color--red-A400{background-color:#ff1744 !important}.mdl-color-text--red-A700{color:#d50000 !important}.mdl-color--red-A700{background-color:#d50000 !important}.mdl-color-text--pink{color:#e91e63 !important}.mdl-color--pink{background-color:#e91e63 !important}.mdl-color-text--pink-50{color:#fce4ec !important}.mdl-color--pink-50{background-color:#fce4ec !important}.mdl-color-text--pink-100{color:#f8bbd0 !important}.mdl-color--pink-100{background-color:#f8bbd0 !important}.mdl-color-text--pink-200{color:#f48fb1 !important}.mdl-color--pink-200{background-color:#f48fb1 !important}.mdl-color-text--pink-300{color:#f06292 !important}.mdl-color--pink-300{background-color:#f06292 !important}.mdl-color-text--pink-400{color:#ec407a !important}.mdl-color--pink-400{background-color:#ec407a !important}.mdl-color-text--pink-500{color:#e91e63 !important}.mdl-color--pink-500{background-color:#e91e63 !important}.mdl-color-text--pink-600{color:#d81b60 !important}.mdl-color--pink-600{background-color:#d81b60 !important}.mdl-color-text--pink-700{color:#c2185b !important}.mdl-color--pink-700{background-color:#c2185b !important}.mdl-color-text--pink-800{color:#ad1457 !important}.mdl-color--pink-800{background-color:#ad1457 !important}.mdl-color-text--pink-900{color:#880e4f !important}.mdl-color--pink-900{background-color:#880e4f !important}.mdl-color-text--pink-A100{color:#ff80ab !important}.mdl-color--pink-A100{background-color:#ff80ab !important}.mdl-color-text--pink-A200{color:#ff4081 !important}.mdl-color--pink-A200{background-color:#ff4081 !important}.mdl-color-text--pink-A400{color:#f50057 !important}.mdl-color--pink-A400{background-color:#f50057 !important}.mdl-color-text--pink-A700{color:#c51162 !important}.mdl-color--pink-A700{background-color:#c51162 !important}.mdl-color-text--purple{color:#9c27b0 !important}.mdl-color--purple{background-color:#9c27b0 !important}.mdl-color-text--purple-50{color:#f3e5f5 !important}.mdl-color--purple-50{background-color:#f3e5f5 !important}.mdl-color-text--purple-100{color:#e1bee7 !important}.mdl-color--purple-100{background-color:#e1bee7 !important}.mdl-color-text--purple-200{color:#ce93d8 !important}.mdl-color--purple-200{background-color:#ce93d8 !important}.mdl-color-text--purple-300{color:#ba68c8 !important}.mdl-color--purple-300{background-color:#ba68c8 !important}.mdl-color-text--purple-400{color:#ab47bc !important}.mdl-color--purple-400{background-color:#ab47bc !important}.mdl-color-text--purple-500{color:#9c27b0 !important}.mdl-color--purple-500{background-color:#9c27b0 !important}.mdl-color-text--purple-600{color:#8e24aa !important}.mdl-color--purple-600{background-color:#8e24aa !important}.mdl-color-text--purple-700{color:#7b1fa2 !important}.mdl-color--purple-700{background-color:#7b1fa2 !important}.mdl-color-text--purple-800{color:#6a1b9a !important}.mdl-color--purple-800{background-color:#6a1b9a !important}.mdl-color-text--purple-900{color:#4a148c !important}.mdl-color--purple-900{background-color:#4a148c !important}.mdl-color-text--purple-A100{color:#ea80fc !important}.mdl-color--purple-A100{background-color:#ea80fc !important}.mdl-color-text--purple-A200{color:#e040fb !important}.mdl-color--purple-A200{background-color:#e040fb !important}.mdl-color-text--purple-A400{color:#d500f9 !important}.mdl-color--purple-A400{background-color:#d500f9 !important}.mdl-color-text--purple-A700{color:#a0f !important}.mdl-color--purple-A700{background-color:#a0f !important}.mdl-color-text--deep-purple{color:#673ab7 !important}.mdl-color--deep-purple{background-color:#673ab7 !important}.mdl-color-text--deep-purple-50{color:#ede7f6 !important}.mdl-color--deep-purple-50{background-color:#ede7f6 !important}.mdl-color-text--deep-purple-100{color:#d1c4e9 !important}.mdl-color--deep-purple-100{background-color:#d1c4e9 !important}.mdl-color-text--deep-purple-200{color:#b39ddb !important}.mdl-color--deep-purple-200{background-color:#b39ddb !important}.mdl-color-text--deep-purple-300{color:#9575cd !important}.mdl-color--deep-purple-300{background-color:#9575cd !important}.mdl-color-text--deep-purple-400{color:#7e57c2 !important}.mdl-color--deep-purple-400{background-color:#7e57c2 !important}.mdl-color-text--deep-purple-500{color:#673ab7 !important}.mdl-color--deep-purple-500{background-color:#673ab7 !important}.mdl-color-text--deep-purple-600{color:#5e35b1 !important}.mdl-color--deep-purple-600{background-color:#5e35b1 !important}.mdl-color-text--deep-purple-700{color:#512da8 !important}.mdl-color--deep-purple-700{background-color:#512da8 !important}.mdl-color-text--deep-purple-800{color:#4527a0 !important}.mdl-color--deep-purple-800{background-color:#4527a0 !important}.mdl-color-text--deep-purple-900{color:#311b92 !important}.mdl-color--deep-purple-900{background-color:#311b92 !important}.mdl-color-text--deep-purple-A100{color:#b388ff !important}.mdl-color--deep-purple-A100{background-color:#b388ff !important}.mdl-color-text--deep-purple-A200{color:#7c4dff !important}.mdl-color--deep-purple-A200{background-color:#7c4dff !important}.mdl-color-text--deep-purple-A400{color:#651fff !important}.mdl-color--deep-purple-A400{background-color:#651fff !important}.mdl-color-text--deep-purple-A700{color:#6200ea !important}.mdl-color--deep-purple-A700{background-color:#6200ea !important}.mdl-color-text--indigo{color:#3f51b5 !important}.mdl-color--indigo{background-color:#3f51b5 !important}.mdl-color-text--indigo-50{color:#e8eaf6 !important}.mdl-color--indigo-50{background-color:#e8eaf6 !important}.mdl-color-text--indigo-100{color:#c5cae9 !important}.mdl-color--indigo-100{background-color:#c5cae9 !important}.mdl-color-text--indigo-200{color:#9fa8da !important}.mdl-color--indigo-200{background-color:#9fa8da !important}.mdl-color-text--indigo-300{color:#7986cb !important}.mdl-color--indigo-300{background-color:#7986cb !important}.mdl-color-text--indigo-400{color:#5c6bc0 !important}.mdl-color--indigo-400{background-color:#5c6bc0 !important}.mdl-color-text--indigo-500{color:#3f51b5 !important}.mdl-color--indigo-500{background-color:#3f51b5 !important}.mdl-color-text--indigo-600{color:#3949ab !important}.mdl-color--indigo-600{background-color:#3949ab !important}.mdl-color-text--indigo-700{color:#303f9f !important}.mdl-color--indigo-700{background-color:#303f9f !important}.mdl-color-text--indigo-800{color:#283593 !important}.mdl-color--indigo-800{background-color:#283593 !important}.mdl-color-text--indigo-900{color:#1a237e !important}.mdl-color--indigo-900{background-color:#1a237e !important}.mdl-color-text--indigo-A100{color:#8c9eff !important}.mdl-color--indigo-A100{background-color:#8c9eff !important}.mdl-color-text--indigo-A200{color:#536dfe !important}.mdl-color--indigo-A200{background-color:#536dfe !important}.mdl-color-text--indigo-A400{color:#3d5afe !important}.mdl-color--indigo-A400{background-color:#3d5afe !important}.mdl-color-text--indigo-A700{color:#304ffe !important}.mdl-color--indigo-A700{background-color:#304ffe !important}.mdl-color-text--blue{color:#2196f3 !important}.mdl-color--blue{background-color:#2196f3 !important}.mdl-color-text--blue-50{color:#e3f2fd !important}.mdl-color--blue-50{background-color:#e3f2fd !important}.mdl-color-text--blue-100{color:#bbdefb !important}.mdl-color--blue-100{background-color:#bbdefb !important}.mdl-color-text--blue-200{color:#90caf9 !important}.mdl-color--blue-200{background-color:#90caf9 !important}.mdl-color-text--blue-300{color:#64b5f6 !important}.mdl-color--blue-300{background-color:#64b5f6 !important}.mdl-color-text--blue-400{color:#42a5f5 !important}.mdl-color--blue-400{background-color:#42a5f5 !important}.mdl-color-text--blue-500{color:#2196f3 !important}.mdl-color--blue-500{background-color:#2196f3 !important}.mdl-color-text--blue-600{color:#1e88e5 !important}.mdl-color--blue-600{background-color:#1e88e5 !important}.mdl-color-text--blue-700{color:#1976d2 !important}.mdl-color--blue-700{background-color:#1976d2 !important}.mdl-color-text--blue-800{color:#1565c0 !important}.mdl-color--blue-800{background-color:#1565c0 !important}.mdl-color-text--blue-900{color:#0d47a1 !important}.mdl-color--blue-900{background-color:#0d47a1 !important}.mdl-color-text--blue-A100{color:#82b1ff !important}.mdl-color--blue-A100{background-color:#82b1ff !important}.mdl-color-text--blue-A200{color:#448aff !important}.mdl-color--blue-A200{background-color:#448aff !important}.mdl-color-text--blue-A400{color:#2979ff !important}.mdl-color--blue-A400{background-color:#2979ff !important}.mdl-color-text--blue-A700{color:#2962ff !important}.mdl-color--blue-A700{background-color:#2962ff !important}.mdl-color-text--light-blue{color:#03a9f4 !important}.mdl-color--light-blue{background-color:#03a9f4 !important}.mdl-color-text--light-blue-50{color:#e1f5fe !important}.mdl-color--light-blue-50{background-color:#e1f5fe !important}.mdl-color-text--light-blue-100{color:#b3e5fc !important}.mdl-color--light-blue-100{background-color:#b3e5fc !important}.mdl-color-text--light-blue-200{color:#81d4fa !important}.mdl-color--light-blue-200{background-color:#81d4fa !important}.mdl-color-text--light-blue-300{color:#4fc3f7 !important}.mdl-color--light-blue-300{background-color:#4fc3f7 !important}.mdl-color-text--light-blue-400{color:#29b6f6 !important}.mdl-color--light-blue-400{background-color:#29b6f6 !important}.mdl-color-text--light-blue-500{color:#03a9f4 !important}.mdl-color--light-blue-500{background-color:#03a9f4 !important}.mdl-color-text--light-blue-600{color:#039be5 !important}.mdl-color--light-blue-600{background-color:#039be5 !important}.mdl-color-text--light-blue-700{color:#0288d1 !important}.mdl-color--light-blue-700{background-color:#0288d1 !important}.mdl-color-text--light-blue-800{color:#0277bd !important}.mdl-color--light-blue-800{background-color:#0277bd !important}.mdl-color-text--light-blue-900{color:#01579b !important}.mdl-color--light-blue-900{background-color:#01579b !important}.mdl-color-text--light-blue-A100{color:#80d8ff !important}.mdl-color--light-blue-A100{background-color:#80d8ff !important}.mdl-color-text--light-blue-A200{color:#40c4ff !important}.mdl-color--light-blue-A200{background-color:#40c4ff !important}.mdl-color-text--light-blue-A400{color:#00b0ff !important}.mdl-color--light-blue-A400{background-color:#00b0ff !important}.mdl-color-text--light-blue-A700{color:#0091ea !important}.mdl-color--light-blue-A700{background-color:#0091ea !important}.mdl-color-text--cyan{color:#00bcd4 !important}.mdl-color--cyan{background-color:#00bcd4 !important}.mdl-color-text--cyan-50{color:#e0f7fa !important}.mdl-color--cyan-50{background-color:#e0f7fa !important}.mdl-color-text--cyan-100{color:#b2ebf2 !important}.mdl-color--cyan-100{background-color:#b2ebf2 !important}.mdl-color-text--cyan-200{color:#80deea !important}.mdl-color--cyan-200{background-color:#80deea !important}.mdl-color-text--cyan-300{color:#4dd0e1 !important}.mdl-color--cyan-300{background-color:#4dd0e1 !important}.mdl-color-text--cyan-400{color:#26c6da !important}.mdl-color--cyan-400{background-color:#26c6da !important}.mdl-color-text--cyan-500{color:#00bcd4 !important}.mdl-color--cyan-500{background-color:#00bcd4 !important}.mdl-color-text--cyan-600{color:#00acc1 !important}.mdl-color--cyan-600{background-color:#00acc1 !important}.mdl-color-text--cyan-700{color:#0097a7 !important}.mdl-color--cyan-700{background-color:#0097a7 !important}.mdl-color-text--cyan-800{color:#00838f !important}.mdl-color--cyan-800{background-color:#00838f !important}.mdl-color-text--cyan-900{color:#006064 !important}.mdl-color--cyan-900{background-color:#006064 !important}.mdl-color-text--cyan-A100{color:#84ffff !important}.mdl-color--cyan-A100{background-color:#84ffff !important}.mdl-color-text--cyan-A200{color:#18ffff !important}.mdl-color--cyan-A200{background-color:#18ffff !important}.mdl-color-text--cyan-A400{color:#00e5ff !important}.mdl-color--cyan-A400{background-color:#00e5ff !important}.mdl-color-text--cyan-A700{color:#00b8d4 !important}.mdl-color--cyan-A700{background-color:#00b8d4 !important}.mdl-color-text--teal{color:#009688 !important}.mdl-color--teal{background-color:#009688 !important}.mdl-color-text--teal-50{color:#e0f2f1 !important}.mdl-color--teal-50{background-color:#e0f2f1 !important}.mdl-color-text--teal-100{color:#b2dfdb !important}.mdl-color--teal-100{background-color:#b2dfdb !important}.mdl-color-text--teal-200{color:#80cbc4 !important}.mdl-color--teal-200{background-color:#80cbc4 !important}.mdl-color-text--teal-300{color:#4db6ac !important}.mdl-color--teal-300{background-color:#4db6ac !important}.mdl-color-text--teal-400{color:#26a69a !important}.mdl-color--teal-400{background-color:#26a69a !important}.mdl-color-text--teal-500{color:#009688 !important}.mdl-color--teal-500{background-color:#009688 !important}.mdl-color-text--teal-600{color:#00897b !important}.mdl-color--teal-600{background-color:#00897b !important}.mdl-color-text--teal-700{color:#00796b !important}.mdl-color--teal-700{background-color:#00796b !important}.mdl-color-text--teal-800{color:#00695c !important}.mdl-color--teal-800{background-color:#00695c !important}.mdl-color-text--teal-900{color:#004d40 !important}.mdl-color--teal-900{background-color:#004d40 !important}.mdl-color-text--teal-A100{color:#a7ffeb !important}.mdl-color--teal-A100{background-color:#a7ffeb !important}.mdl-color-text--teal-A200{color:#64ffda !important}.mdl-color--teal-A200{background-color:#64ffda !important}.mdl-color-text--teal-A400{color:#1de9b6 !important}.mdl-color--teal-A400{background-color:#1de9b6 !important}.mdl-color-text--teal-A700{color:#00bfa5 !important}.mdl-color--teal-A700{background-color:#00bfa5 !important}.mdl-color-text--green{color:#4caf50 !important}.mdl-color--green{background-color:#4caf50 !important}.mdl-color-text--green-50{color:#e8f5e9 !important}.mdl-color--green-50{background-color:#e8f5e9 !important}.mdl-color-text--green-100{color:#c8e6c9 !important}.mdl-color--green-100{background-color:#c8e6c9 !important}.mdl-color-text--green-200{color:#a5d6a7 !important}.mdl-color--green-200{background-color:#a5d6a7 !important}.mdl-color-text--green-300{color:#81c784 !important}.mdl-color--green-300{background-color:#81c784 !important}.mdl-color-text--green-400{color:#66bb6a !important}.mdl-color--green-400{background-color:#66bb6a !important}.mdl-color-text--green-500{color:#4caf50 !important}.mdl-color--green-500{background-color:#4caf50 !important}.mdl-color-text--green-600{color:#43a047 !important}.mdl-color--green-600{background-color:#43a047 !important}.mdl-color-text--green-700{color:#388e3c !important}.mdl-color--green-700{background-color:#388e3c !important}.mdl-color-text--green-800{color:#2e7d32 !important}.mdl-color--green-800{background-color:#2e7d32 !important}.mdl-color-text--green-900{color:#1b5e20 !important}.mdl-color--green-900{background-color:#1b5e20 !important}.mdl-color-text--green-A100{color:#b9f6ca !important}.mdl-color--green-A100{background-color:#b9f6ca !important}.mdl-color-text--green-A200{color:#69f0ae !important}.mdl-color--green-A200{background-color:#69f0ae !important}.mdl-color-text--green-A400{color:#00e676 !important}.mdl-color--green-A400{background-color:#00e676 !important}.mdl-color-text--green-A700{color:#00c853 !important}.mdl-color--green-A700{background-color:#00c853 !important}.mdl-color-text--light-green{color:#8bc34a !important}.mdl-color--light-green{background-color:#8bc34a !important}.mdl-color-text--light-green-50{color:#f1f8e9 !important}.mdl-color--light-green-50{background-color:#f1f8e9 !important}.mdl-color-text--light-green-100{color:#dcedc8 !important}.mdl-color--light-green-100{background-color:#dcedc8 !important}.mdl-color-text--light-green-200{color:#c5e1a5 !important}.mdl-color--light-green-200{background-color:#c5e1a5 !important}.mdl-color-text--light-green-300{color:#aed581 !important}.mdl-color--light-green-300{background-color:#aed581 !important}.mdl-color-text--light-green-400{color:#9ccc65 !important}.mdl-color--light-green-400{background-color:#9ccc65 !important}.mdl-color-text--light-green-500{color:#8bc34a !important}.mdl-color--light-green-500{background-color:#8bc34a !important}.mdl-color-text--light-green-600{color:#7cb342 !important}.mdl-color--light-green-600{background-color:#7cb342 !important}.mdl-color-text--light-green-700{color:#689f38 !important}.mdl-color--light-green-700{background-color:#689f38 !important}.mdl-color-text--light-green-800{color:#558b2f !important}.mdl-color--light-green-800{background-color:#558b2f !important}.mdl-color-text--light-green-900{color:#33691e !important}.mdl-color--light-green-900{background-color:#33691e !important}.mdl-color-text--light-green-A100{color:#ccff90 !important}.mdl-color--light-green-A100{background-color:#ccff90 !important}.mdl-color-text--light-green-A200{color:#b2ff59 !important}.mdl-color--light-green-A200{background-color:#b2ff59 !important}.mdl-color-text--light-green-A400{color:#76ff03 !important}.mdl-color--light-green-A400{background-color:#76ff03 !important}.mdl-color-text--light-green-A700{color:#64dd17 !important}.mdl-color--light-green-A700{background-color:#64dd17 !important}.mdl-color-text--lime{color:#cddc39 !important}.mdl-color--lime{background-color:#cddc39 !important}.mdl-color-text--lime-50{color:#f9fbe7 !important}.mdl-color--lime-50{background-color:#f9fbe7 !important}.mdl-color-text--lime-100{color:#f0f4c3 !important}.mdl-color--lime-100{background-color:#f0f4c3 !important}.mdl-color-text--lime-200{color:#e6ee9c !important}.mdl-color--lime-200{background-color:#e6ee9c !important}.mdl-color-text--lime-300{color:#dce775 !important}.mdl-color--lime-300{background-color:#dce775 !important}.mdl-color-text--lime-400{color:#d4e157 !important}.mdl-color--lime-400{background-color:#d4e157 !important}.mdl-color-text--lime-500{color:#cddc39 !important}.mdl-color--lime-500{background-color:#cddc39 !important}.mdl-color-text--lime-600{color:#c0ca33 !important}.mdl-color--lime-600{background-color:#c0ca33 !important}.mdl-color-text--lime-700{color:#afb42b !important}.mdl-color--lime-700{background-color:#afb42b !important}.mdl-color-text--lime-800{color:#9e9d24 !important}.mdl-color--lime-800{background-color:#9e9d24 !important}.mdl-color-text--lime-900{color:#827717 !important}.mdl-color--lime-900{background-color:#827717 !important}.mdl-color-text--lime-A100{color:#f4ff81 !important}.mdl-color--lime-A100{background-color:#f4ff81 !important}.mdl-color-text--lime-A200{color:#eeff41 !important}.mdl-color--lime-A200{background-color:#eeff41 !important}.mdl-color-text--lime-A400{color:#c6ff00 !important}.mdl-color--lime-A400{background-color:#c6ff00 !important}.mdl-color-text--lime-A700{color:#aeea00 !important}.mdl-color--lime-A700{background-color:#aeea00 !important}.mdl-color-text--yellow{color:#ffeb3b !important}.mdl-color--yellow{background-color:#ffeb3b !important}.mdl-color-text--yellow-50{color:#fffde7 !important}.mdl-color--yellow-50{background-color:#fffde7 !important}.mdl-color-text--yellow-100{color:#fff9c4 !important}.mdl-color--yellow-100{background-color:#fff9c4 !important}.mdl-color-text--yellow-200{color:#fff59d !important}.mdl-color--yellow-200{background-color:#fff59d !important}.mdl-color-text--yellow-300{color:#fff176 !important}.mdl-color--yellow-300{background-color:#fff176 !important}.mdl-color-text--yellow-400{color:#ffee58 !important}.mdl-color--yellow-400{background-color:#ffee58 !important}.mdl-color-text--yellow-500{color:#ffeb3b !important}.mdl-color--yellow-500{background-color:#ffeb3b !important}.mdl-color-text--yellow-600{color:#fdd835 !important}.mdl-color--yellow-600{background-color:#fdd835 !important}.mdl-color-text--yellow-700{color:#fbc02d !important}.mdl-color--yellow-700{background-color:#fbc02d !important}.mdl-color-text--yellow-800{color:#f9a825 !important}.mdl-color--yellow-800{background-color:#f9a825 !important}.mdl-color-text--yellow-900{color:#f57f17 !important}.mdl-color--yellow-900{background-color:#f57f17 !important}.mdl-color-text--yellow-A100{color:#ffff8d !important}.mdl-color--yellow-A100{background-color:#ffff8d !important}.mdl-color-text--yellow-A200{color:#ff0 !important}.mdl-color--yellow-A200{background-color:#ff0 !important}.mdl-color-text--yellow-A400{color:#ffea00 !important}.mdl-color--yellow-A400{background-color:#ffea00 !important}.mdl-color-text--yellow-A700{color:#ffd600 !important}.mdl-color--yellow-A700{background-color:#ffd600 !important}.mdl-color-text--amber{color:#ffc107 !important}.mdl-color--amber{background-color:#ffc107 !important}.mdl-color-text--amber-50{color:#fff8e1 !important}.mdl-color--amber-50{background-color:#fff8e1 !important}.mdl-color-text--amber-100{color:#ffecb3 !important}.mdl-color--amber-100{background-color:#ffecb3 !important}.mdl-color-text--amber-200{color:#ffe082 !important}.mdl-color--amber-200{background-color:#ffe082 !important}.mdl-color-text--amber-300{color:#ffd54f !important}.mdl-color--amber-300{background-color:#ffd54f !important}.mdl-color-text--amber-400{color:#ffca28 !important}.mdl-color--amber-400{background-color:#ffca28 !important}.mdl-color-text--amber-500{color:#ffc107 !important}.mdl-color--amber-500{background-color:#ffc107 !important}.mdl-color-text--amber-600{color:#ffb300 !important}.mdl-color--amber-600{background-color:#ffb300 !important}.mdl-color-text--amber-700{color:#ffa000 !important}.mdl-color--amber-700{background-color:#ffa000 !important}.mdl-color-text--amber-800{color:#ff8f00 !important}.mdl-color--amber-800{background-color:#ff8f00 !important}.mdl-color-text--amber-900{color:#ff6f00 !important}.mdl-color--amber-900{background-color:#ff6f00 !important}.mdl-color-text--amber-A100{color:#ffe57f !important}.mdl-color--amber-A100{background-color:#ffe57f !important}.mdl-color-text--amber-A200{color:#ffd740 !important}.mdl-color--amber-A200{background-color:#ffd740 !important}.mdl-color-text--amber-A400{color:#ffc400 !important}.mdl-color--amber-A400{background-color:#ffc400 !important}.mdl-color-text--amber-A700{color:#ffab00 !important}.mdl-color--amber-A700{background-color:#ffab00 !important}.mdl-color-text--orange{color:#ff9800 !important}.mdl-color--orange{background-color:#ff9800 !important}.mdl-color-text--orange-50{color:#fff3e0 !important}.mdl-color--orange-50{background-color:#fff3e0 !important}.mdl-color-text--orange-100{color:#ffe0b2 !important}.mdl-color--orange-100{background-color:#ffe0b2 !important}.mdl-color-text--orange-200{color:#ffcc80 !important}.mdl-color--orange-200{background-color:#ffcc80 !important}.mdl-color-text--orange-300{color:#ffb74d !important}.mdl-color--orange-300{background-color:#ffb74d !important}.mdl-color-text--orange-400{color:#ffa726 !important}.mdl-color--orange-400{background-color:#ffa726 !important}.mdl-color-text--orange-500{color:#ff9800 !important}.mdl-color--orange-500{background-color:#ff9800 !important}.mdl-color-text--orange-600{color:#fb8c00 !important}.mdl-color--orange-600{background-color:#fb8c00 !important}.mdl-color-text--orange-700{color:#f57c00 !important}.mdl-color--orange-700{background-color:#f57c00 !important}.mdl-color-text--orange-800{color:#ef6c00 !important}.mdl-color--orange-800{background-color:#ef6c00 !important}.mdl-color-text--orange-900{color:#e65100 !important}.mdl-color--orange-900{background-color:#e65100 !important}.mdl-color-text--orange-A100{color:#ffd180 !important}.mdl-color--orange-A100{background-color:#ffd180 !important}.mdl-color-text--orange-A200{color:#ffab40 !important}.mdl-color--orange-A200{background-color:#ffab40 !important}.mdl-color-text--orange-A400{color:#ff9100 !important}.mdl-color--orange-A400{background-color:#ff9100 !important}.mdl-color-text--orange-A700{color:#ff6d00 !important}.mdl-color--orange-A700{background-color:#ff6d00 !important}.mdl-color-text--deep-orange{color:#ff5722 !important}.mdl-color--deep-orange{background-color:#ff5722 !important}.mdl-color-text--deep-orange-50{color:#fbe9e7 !important}.mdl-color--deep-orange-50{background-color:#fbe9e7 !important}.mdl-color-text--deep-orange-100{color:#ffccbc !important}.mdl-color--deep-orange-100{background-color:#ffccbc !important}.mdl-color-text--deep-orange-200{color:#ffab91 !important}.mdl-color--deep-orange-200{background-color:#ffab91 !important}.mdl-color-text--deep-orange-300{color:#ff8a65 !important}.mdl-color--deep-orange-300{background-color:#ff8a65 !important}.mdl-color-text--deep-orange-400{color:#ff7043 !important}.mdl-color--deep-orange-400{background-color:#ff7043 !important}.mdl-color-text--deep-orange-500{color:#ff5722 !important}.mdl-color--deep-orange-500{background-color:#ff5722 !important}.mdl-color-text--deep-orange-600{color:#f4511e !important}.mdl-color--deep-orange-600{background-color:#f4511e !important}.mdl-color-text--deep-orange-700{color:#e64a19 !important}.mdl-color--deep-orange-700{background-color:#e64a19 !important}.mdl-color-text--deep-orange-800{color:#d84315 !important}.mdl-color--deep-orange-800{background-color:#d84315 !important}.mdl-color-text--deep-orange-900{color:#bf360c !important}.mdl-color--deep-orange-900{background-color:#bf360c !important}.mdl-color-text--deep-orange-A100{color:#ff9e80 !important}.mdl-color--deep-orange-A100{background-color:#ff9e80 !important}.mdl-color-text--deep-orange-A200{color:#ff6e40 !important}.mdl-color--deep-orange-A200{background-color:#ff6e40 !important}.mdl-color-text--deep-orange-A400{color:#ff3d00 !important}.mdl-color--deep-orange-A400{background-color:#ff3d00 !important}.mdl-color-text--deep-orange-A700{color:#dd2c00 !important}.mdl-color--deep-orange-A700{background-color:#dd2c00 !important}.mdl-color-text--brown{color:#795548 !important}.mdl-color--brown{background-color:#795548 !important}.mdl-color-text--brown-50{color:#efebe9 !important}.mdl-color--brown-50{background-color:#efebe9 !important}.mdl-color-text--brown-100{color:#d7ccc8 !important}.mdl-color--brown-100{background-color:#d7ccc8 !important}.mdl-color-text--brown-200{color:#bcaaa4 !important}.mdl-color--brown-200{background-color:#bcaaa4 !important}.mdl-color-text--brown-300{color:#a1887f !important}.mdl-color--brown-300{background-color:#a1887f !important}.mdl-color-text--brown-400{color:#8d6e63 !important}.mdl-color--brown-400{background-color:#8d6e63 !important}.mdl-color-text--brown-500{color:#795548 !important}.mdl-color--brown-500{background-color:#795548 !important}.mdl-color-text--brown-600{color:#6d4c41 !important}.mdl-color--brown-600{background-color:#6d4c41 !important}.mdl-color-text--brown-700{color:#5d4037 !important}.mdl-color--brown-700{background-color:#5d4037 !important}.mdl-color-text--brown-800{color:#4e342e !important}.mdl-color--brown-800{background-color:#4e342e !important}.mdl-color-text--brown-900{color:#3e2723 !important}.mdl-color--brown-900{background-color:#3e2723 !important}.mdl-color-text--grey{color:#9e9e9e !important}.mdl-color--grey{background-color:#9e9e9e !important}.mdl-color-text--grey-50{color:#fafafa !important}.mdl-color--grey-50{background-color:#fafafa !important}.mdl-color-text--grey-100{color:#f5f5f5 !important}.mdl-color--grey-100{background-color:#f5f5f5 !important}.mdl-color-text--grey-200{color:#eee !important}.mdl-color--grey-200{background-color:#eee !important}.mdl-color-text--grey-300{color:#e0e0e0 !important}.mdl-color--grey-300{background-color:#e0e0e0 !important}.mdl-color-text--grey-400{color:#bdbdbd !important}.mdl-color--grey-400{background-color:#bdbdbd !important}.mdl-color-text--grey-500{color:#9e9e9e !important}.mdl-color--grey-500{background-color:#9e9e9e !important}.mdl-color-text--grey-600{color:#757575 !important}.mdl-color--grey-600{background-color:#757575 !important}.mdl-color-text--grey-700{color:#616161 !important}.mdl-color--grey-700{background-color:#616161 !important}.mdl-color-text--grey-800{color:#424242 !important}.mdl-color--grey-800{background-color:#424242 !important}.mdl-color-text--grey-900{color:#212121 !important}.mdl-color--grey-900{background-color:#212121 !important}.mdl-color-text--blue-grey{color:#607d8b !important}.mdl-color--blue-grey{background-color:#607d8b !important}.mdl-color-text--blue-grey-50{color:#eceff1 !important}.mdl-color--blue-grey-50{background-color:#eceff1 !important}.mdl-color-text--blue-grey-100{color:#cfd8dc !important}.mdl-color--blue-grey-100{background-color:#cfd8dc !important}.mdl-color-text--blue-grey-200{color:#b0bec5 !important}.mdl-color--blue-grey-200{background-color:#b0bec5 !important}.mdl-color-text--blue-grey-300{color:#90a4ae !important}.mdl-color--blue-grey-300{background-color:#90a4ae !important}.mdl-color-text--blue-grey-400{color:#78909c !important}.mdl-color--blue-grey-400{background-color:#78909c !important}.mdl-color-text--blue-grey-500{color:#607d8b !important}.mdl-color--blue-grey-500{background-color:#607d8b !important}.mdl-color-text--blue-grey-600{color:#546e7a !important}.mdl-color--blue-grey-600{background-color:#546e7a !important}.mdl-color-text--blue-grey-700{color:#455a64 !important}.mdl-color--blue-grey-700{background-color:#455a64 !important}.mdl-color-text--blue-grey-800{color:#37474f !important}.mdl-color--blue-grey-800{background-color:#37474f !important}.mdl-color-text--blue-grey-900{color:#263238 !important}.mdl-color--blue-grey-900{background-color:#263238 !important}.mdl-color--black{background-color:#000 !important}.mdl-color-text--black{color:#000 !important}.mdl-color--white{background-color:#fff !important}.mdl-color-text--white{color:#fff !important}.mdl-color--primary{background-color:rgb(63,81,181)!important}.mdl-color--primary-contrast{background-color:rgb(255,255,255)!important}.mdl-color--primary-dark{background-color:rgb(48,63,159)!important}.mdl-color--accent{background-color:rgb(224,64,251)!important}.mdl-color--accent-contrast{background-color:rgb(255,255,255)!important}.mdl-color-text--primary{color:rgb(63,81,181)!important}.mdl-color-text--primary-contrast{color:rgb(255,255,255)!important}.mdl-color-text--primary-dark{color:rgb(48,63,159)!important}.mdl-color-text--accent{color:rgb(156,39,176)!important}.mdl-color-text--accent-contrast{color:rgb(255,255,255)!important}.mdl-ripple{background:#000;border-radius:50%;height:50px;left:0;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:50px;overflow:hidden}.mdl-ripple.is-animating{transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1);transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1),-webkit-transform .3s cubic-bezier(0,0,.2,1)}.mdl-ripple.is-visible{opacity:.3}.mdl-animation--default,.mdl-animation--fast-out-slow-in{transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-animation--linear-out-slow-in{transition-timing-function:cubic-bezier(0,0,.2,1)}.mdl-animation--fast-out-linear-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.mdl-badge{position:relative;white-space:nowrap;margin-right:24px}.mdl-badge:not([data-badge]){margin-right:auto}.mdl-badge[data-badge]:after{content:attr(data-badge);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:absolute;top:-11px;right:-24px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:600;font-size:12px;width:22px;height:22px;border-radius:50%;background:rgb(224,64,251);color:rgb(255,255,255)}.mdl-button .mdl-badge[data-badge]:after{top:-10px;right:-5px}.mdl-badge.mdl-badge--no-background[data-badge]:after{color:rgb(224,64,251);background:rgba(255,255,255,.2);box-shadow:0 0 1px gray}.mdl-badge.mdl-badge--overlap{margin-right:10px}.mdl-badge.mdl-badge--overlap:after{right:-10px}.mdl-button{background:0 0;border:none;border-radius:2px;color:#000;position:relative;height:36px;margin:0;min-width:64px;padding:0 16px;display:inline-block;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;letter-spacing:0;overflow:hidden;will-change:box-shadow;transition:box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);outline:none;cursor:pointer;text-decoration:none;text-align:center;line-height:36px;vertical-align:middle}.mdl-button::-moz-focus-inner{border:0}.mdl-button:hover{background-color:rgba(158,158,158,.2)}.mdl-button:focus:not(:active){background-color:rgba(0,0,0,.12)}.mdl-button:active{background-color:rgba(158,158,158,.4)}.mdl-button.mdl-button--colored{color:rgb(63,81,181)}.mdl-button.mdl-button--colored:focus:not(:active){background-color:rgba(0,0,0,.12)}input.mdl-button[type="submit"]{-webkit-appearance:none}.mdl-button--raised{background:rgba(158,158,158,.2);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-button--raised:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--raised:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--raised.mdl-button--colored{background:rgb(63,81,181);color:rgb(255,255,255)}.mdl-button--raised.mdl-button--colored:hover{background-color:rgb(63,81,181)}.mdl-button--raised.mdl-button--colored:active{background-color:rgb(63,81,181)}.mdl-button--raised.mdl-button--colored:focus:not(:active){background-color:rgb(63,81,181)}.mdl-button--raised.mdl-button--colored .mdl-ripple{background:rgb(255,255,255)}.mdl-button--fab{border-radius:50%;font-size:24px;height:56px;margin:auto;min-width:56px;width:56px;padding:0;overflow:hidden;background:rgba(158,158,158,.2);box-shadow:0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);position:relative;line-height:normal}.mdl-button--fab .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--fab.mdl-button--mini-fab{height:40px;min-width:40px;width:40px}.mdl-button--fab .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button--fab:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--fab:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--fab.mdl-button--colored{background:rgb(224,64,251);color:rgb(255,255,255)}.mdl-button--fab.mdl-button--colored:hover{background-color:rgb(224,64,251)}.mdl-button--fab.mdl-button--colored:focus:not(:active){background-color:rgb(224,64,251)}.mdl-button--fab.mdl-button--colored:active{background-color:rgb(224,64,251)}.mdl-button--fab.mdl-button--colored .mdl-ripple{background:rgb(255,255,255)}.mdl-button--icon{border-radius:50%;font-size:24px;height:32px;margin-left:0;margin-right:0;min-width:32px;width:32px;padding:0;overflow:hidden;color:inherit;line-height:normal}.mdl-button--icon .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon{height:24px;min-width:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon .material-icons{top:0;left:0}.mdl-button--icon .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button__ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-button[disabled] .mdl-button__ripple-container .mdl-ripple,.mdl-button.mdl-button--disabled .mdl-button__ripple-container .mdl-ripple{background-color:transparent}.mdl-button--primary.mdl-button--primary{color:rgb(63,81,181)}.mdl-button--primary.mdl-button--primary .mdl-ripple{background:rgb(255,255,255)}.mdl-button--primary.mdl-button--primary.mdl-button--raised,.mdl-button--primary.mdl-button--primary.mdl-button--fab{color:rgb(255,255,255);background-color:rgb(63,81,181)}.mdl-button--accent.mdl-button--accent{color:rgb(224,64,251)}.mdl-button--accent.mdl-button--accent .mdl-ripple{background:rgb(255,255,255)}.mdl-button--accent.mdl-button--accent.mdl-button--raised,.mdl-button--accent.mdl-button--accent.mdl-button--fab{color:rgb(255,255,255);background-color:rgb(224,64,251)}.mdl-button[disabled][disabled],.mdl-button.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26);cursor:default;background-color:transparent}.mdl-button--fab[disabled][disabled],.mdl-button--fab.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-button--raised[disabled][disabled],.mdl-button--raised.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26);box-shadow:none}.mdl-button--colored[disabled][disabled],.mdl-button--colored.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26)}.mdl-button .material-icons{vertical-align:middle}.mdl-card{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;font-size:16px;font-weight:400;min-height:200px;overflow:hidden;width:330px;z-index:1;position:relative;background:#fff;border-radius:2px;box-sizing:border-box}.mdl-card__media{background-color:rgb(224,64,251);background-repeat:repeat;background-position:50% 50%;background-size:cover;background-origin:padding-box;background-attachment:scroll;box-sizing:border-box}.mdl-card__title{-webkit-align-items:center;-ms-flex-align:center;align-items:center;color:#000;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;line-height:normal;padding:16px;-webkit-perspective-origin:165px 56px;perspective-origin:165px 56px;-webkit-transform-origin:165px 56px;transform-origin:165px 56px;box-sizing:border-box}.mdl-card__title.mdl-card--border{border-bottom:1px solid rgba(0,0,0,.1)}.mdl-card__title-text{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;color:inherit;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:24px;font-weight:300;line-height:normal;overflow:hidden;-webkit-transform-origin:149px 48px;transform-origin:149px 48px;margin:0}.mdl-card__subtitle-text{font-size:14px;color:rgba(0,0,0,.54);margin:0}.mdl-card__supporting-text{color:rgba(0,0,0,.54);font-size:1rem;line-height:18px;overflow:hidden;padding:16px;width:90%}.mdl-card__actions{font-size:16px;line-height:normal;width:100%;background-color:transparent;padding:8px;box-sizing:border-box}.mdl-card__actions.mdl-card--border{border-top:1px solid rgba(0,0,0,.1)}.mdl-card--expand{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-card__menu{position:absolute;right:16px;top:16px}.mdl-checkbox{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0}.mdl-checkbox.is-upgraded{padding-left:24px}.mdl-checkbox__input{line-height:24px}.mdl-checkbox.is-upgraded .mdl-checkbox__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-checkbox__box-outline{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;overflow:hidden;border:2px solid rgba(0,0,0,.54);border-radius:2px;z-index:2}.mdl-checkbox.is-checked .mdl-checkbox__box-outline{border:2px solid rgb(63,81,181)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__box-outline,.mdl-checkbox.is-disabled .mdl-checkbox__box-outline{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__focus-helper{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:transparent}.mdl-checkbox.is-focused .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-checkbox.is-focused.is-checked .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(63,81,181,.26);background-color:rgba(63,81,181,.26)}.mdl-checkbox__tick-outline{position:absolute;top:0;left:0;height:100%;width:100%;-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");background:0 0;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background}.mdl-checkbox.is-checked .mdl-checkbox__tick-outline{background:rgb(63,81,181)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}fieldset[disabled] .mdl-checkbox.is-checked .mdl-checkbox__tick-outline,.mdl-checkbox.is-checked.is-disabled .mdl-checkbox__tick-outline{background:rgba(0,0,0,.26)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}.mdl-checkbox__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0}fieldset[disabled] .mdl-checkbox .mdl-checkbox__label,.mdl-checkbox.is-disabled .mdl-checkbox__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__ripple-container{position:absolute;z-index:2;top:-6px;left:-10px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-checkbox__ripple-container .mdl-ripple{background:rgb(63,81,181)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container{cursor:auto}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container .mdl-ripple,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container .mdl-ripple{background:0 0}.mdl-chip{height:32px;font-family:"Roboto","Helvetica","Arial",sans-serif;line-height:32px;padding:0 12px;border:0;border-radius:16px;background-color:#dedede;display:inline-block;color:rgba(0,0,0,.87);margin:2px 0;font-size:0;white-space:nowrap}.mdl-chip__text{font-size:13px;vertical-align:middle;display:inline-block}.mdl-chip__action{height:24px;width:24px;background:0 0;opacity:.54;cursor:pointer;padding:0;margin:0 0 0 4px;font-size:13px;text-decoration:none;color:rgba(0,0,0,.87);border:none;outline:none}.mdl-chip__action,.mdl-chip__contact{display:inline-block;vertical-align:middle;overflow:hidden;text-align:center}.mdl-chip__contact{height:32px;width:32px;border-radius:16px;margin-right:8px;font-size:18px;line-height:32px}.mdl-chip:focus{outline:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-chip:active{background-color:#d6d6d6}.mdl-chip--deletable{padding-right:4px}.mdl-chip--contact{padding-left:0}.mdl-data-table{position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff}.mdl-data-table thead{padding-bottom:3px}.mdl-data-table thead .mdl-data-table__select{margin-top:0}.mdl-data-table tbody tr{position:relative;height:48px;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background-color}.mdl-data-table tbody tr.is-selected{background-color:#e0e0e0}.mdl-data-table tbody tr:hover{background-color:#eee}.mdl-data-table td{text-align:right}.mdl-data-table th{padding:0 18px 12px 18px;text-align:right}.mdl-data-table td:first-of-type,.mdl-data-table th:first-of-type{padding-left:24px}.mdl-data-table td:last-of-type,.mdl-data-table th:last-of-type{padding-right:24px}.mdl-data-table td{position:relative;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px;box-sizing:border-box}.mdl-data-table td,.mdl-data-table td .mdl-data-table__select{vertical-align:middle}.mdl-data-table th{position:relative;vertical-align:bottom;text-overflow:ellipsis;font-weight:700;line-height:24px;letter-spacing:0;height:48px;font-size:12px;color:rgba(0,0,0,.54);padding-bottom:8px;box-sizing:border-box}.mdl-data-table th.mdl-data-table__header--sorted-ascending,.mdl-data-table th.mdl-data-table__header--sorted-descending{color:rgba(0,0,0,.87)}.mdl-data-table th.mdl-data-table__header--sorted-ascending:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:before{font-family:'Material Icons';font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased;font-size:16px;content:"\e5d8";margin-right:5px;vertical-align:sub}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover{cursor:pointer}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover:before{color:rgba(0,0,0,.26)}.mdl-data-table th.mdl-data-table__header--sorted-descending:before{content:"\e5db"}.mdl-data-table__select{width:16px}.mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric{text-align:left}.mdl-dialog{border:none;box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2);width:280px}.mdl-dialog__title{padding:24px 24px 0;margin:0;font-size:2.5rem}.mdl-dialog__actions{padding:8px 8px 8px 24px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.mdl-dialog__actions>*{margin-right:8px;height:36px}.mdl-dialog__actions>*:first-child{margin-right:0}.mdl-dialog__actions--full-width{padding:0 0 8px}.mdl-dialog__actions--full-width>*{height:48px;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;padding-right:16px;margin-right:0;text-align:right}.mdl-dialog__content{padding:20px 24px 24px;color:rgba(0,0,0,.54)}.mdl-mega-footer{padding:16px 40px;color:#9e9e9e;background-color:#424242}.mdl-mega-footer--top-section:after,.mdl-mega-footer--middle-section:after,.mdl-mega-footer--bottom-section:after,.mdl-mega-footer__top-section:after,.mdl-mega-footer__middle-section:after,.mdl-mega-footer__bottom-section:after{content:'';display:block;clear:both}.mdl-mega-footer--left-section,.mdl-mega-footer__left-section,.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{margin-bottom:16px}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:block;margin-bottom:16px;color:inherit;text-decoration:none}@media screen and (min-width:760px){.mdl-mega-footer--left-section,.mdl-mega-footer__left-section{float:left}.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{float:right}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:inline-block;margin-left:16px;line-height:36px;vertical-align:middle}}.mdl-mega-footer--social-btn,.mdl-mega-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{display:block;position:relative}@media screen and (min-width:760px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{width:33%}.mdl-mega-footer--drop-down-section:nth-child(1),.mdl-mega-footer--drop-down-section:nth-child(2),.mdl-mega-footer__drop-down-section:nth-child(1),.mdl-mega-footer__drop-down-section:nth-child(2){float:left}.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(3){float:right}.mdl-mega-footer--drop-down-section:nth-child(3):after,.mdl-mega-footer__drop-down-section:nth-child(3):after{clear:right}.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section:nth-child(4){clear:right;float:right}.mdl-mega-footer--middle-section:after,.mdl-mega-footer__middle-section:after{content:'';display:block;clear:both}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:0}}@media screen and (min-width:1024px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section,.mdl-mega-footer__drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(4){width:24%;float:left}}.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{position:absolute;width:100%;height:55.8px;padding:32px;margin:-16px 0 0;cursor:pointer;z-index:1;opacity:0}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CE'}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list{display:none}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CF'}.mdl-mega-footer--heading,.mdl-mega-footer__heading{position:relative;width:100%;padding-right:39.8px;margin-bottom:16px;box-sizing:border-box;font-size:14px;line-height:23.8px;font-weight:500;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#e0e0e0}.mdl-mega-footer--heading:after,.mdl-mega-footer__heading:after{content:'';position:absolute;top:0;right:0;display:block;width:23.8px;height:23.8px;background-size:cover}.mdl-mega-footer--link-list,.mdl-mega-footer__link-list{list-style:none;padding:0;margin:0 0 32px}.mdl-mega-footer--link-list:after,.mdl-mega-footer__link-list:after{clear:both;display:block;content:''}.mdl-mega-footer--link-list li,.mdl-mega-footer__link-list li{font-size:14px;font-weight:400;letter-spacing:0;line-height:20px}.mdl-mega-footer--link-list a,.mdl-mega-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}@media screen and (min-width:760px){.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{display:none}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{content:''}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list{display:block}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{content:''}}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:16px;margin-bottom:16px}.mdl-logo{margin-bottom:16px;color:#fff}.mdl-mega-footer--bottom-section .mdl-mega-footer--link-list li,.mdl-mega-footer__bottom-section .mdl-mega-footer__link-list li{float:left;margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-logo{float:left;margin-bottom:0;margin-right:16px}}.mdl-mini-footer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:32px 16px;color:#9e9e9e;background-color:#424242}.mdl-mini-footer:after{content:'';display:block}.mdl-mini-footer .mdl-logo{line-height:36px}.mdl-mini-footer--link-list,.mdl-mini-footer__link-list{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;list-style:none;margin:0;padding:0}.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{line-height:36px}}.mdl-mini-footer--link-list a,.mdl-mini-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}.mdl-mini-footer--left-section,.mdl-mini-footer__left-section{display:inline-block;-webkit-order:0;-ms-flex-order:0;order:0}.mdl-mini-footer--right-section,.mdl-mini-footer__right-section{display:inline-block;-webkit-order:1;-ms-flex-order:1;order:1}.mdl-mini-footer--social-btn,.mdl-mini-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-icon-toggle{position:relative;z-index:1;vertical-align:middle;display:inline-block;height:32px;margin:0;padding:0}.mdl-icon-toggle__input{line-height:32px}.mdl-icon-toggle.is-upgraded .mdl-icon-toggle__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-icon-toggle__label{display:inline-block;position:relative;cursor:pointer;height:32px;width:32px;min-width:32px;color:#616161;border-radius:50%;padding:0;margin-left:0;margin-right:0;text-align:center;background-color:transparent;will-change:background-color;transition:background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1)}.mdl-icon-toggle__label.material-icons{line-height:32px;font-size:24px}.mdl-icon-toggle.is-checked .mdl-icon-toggle__label{color:rgb(63,81,181)}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__label{color:rgba(0,0,0,.26);cursor:auto;transition:none}.mdl-icon-toggle.is-focused .mdl-icon-toggle__label{background-color:rgba(0,0,0,.12)}.mdl-icon-toggle.is-focused.is-checked .mdl-icon-toggle__label{background-color:rgba(63,81,181,.26)}.mdl-icon-toggle__ripple-container{position:absolute;z-index:2;top:-2px;left:-2px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-icon-toggle__ripple-container .mdl-ripple{background:#616161}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container{cursor:auto}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container .mdl-ripple{background:0 0}.mdl-list{display:block;padding:8px 0;list-style:none}.mdl-list__item{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:.04em;line-height:1;min-height:48px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;padding:16px;cursor:default;color:rgba(0,0,0,.87);overflow:hidden}.mdl-list__item,.mdl-list__item .mdl-list__item-primary-content{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.mdl-list__item .mdl-list__item-primary-content{-webkit-order:0;-ms-flex-order:0;order:0;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2;text-decoration:none}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-icon{margin-right:32px}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-avatar{margin-right:16px}.mdl-list__item .mdl-list__item-secondary-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column;-ms-flex-flow:column;flex-flow:column;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;margin-left:16px}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-action label{display:inline}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-info{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;color:rgba(0,0,0,.54)}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-sub-header{padding:0 0 0 16px}.mdl-list__item-icon,.mdl-list__item-icon.material-icons{height:24px;width:24px;font-size:24px;box-sizing:border-box;color:#757575}.mdl-list__item-avatar,.mdl-list__item-avatar.material-icons{height:40px;width:40px;box-sizing:border-box;border-radius:50%;background-color:#757575;font-size:40px;color:#fff}.mdl-list__item--two-line{height:72px}.mdl-list__item--two-line .mdl-list__item-primary-content{height:36px;line-height:20px;display:block}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-avatar{float:left}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left;margin-top:6px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-secondary-content{height:36px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-sub-title{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-list__item--three-line{height:88px}.mdl-list__item--three-line .mdl-list__item-primary-content{height:52px;line-height:20px;display:block}.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-avatar,.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left}.mdl-list__item--three-line .mdl-list__item-secondary-content{height:52px}.mdl-list__item--three-line .mdl-list__item-text-body{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;height:52px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-menu__container{display:block;margin:0;padding:0;border:none;position:absolute;overflow:visible;height:0;width:0;visibility:hidden;z-index:-1}.mdl-menu__container.is-visible,.mdl-menu__container.is-animating{z-index:999;visibility:visible}.mdl-menu__outline{display:block;background:#fff;margin:0;padding:0;border:none;border-radius:2px;position:absolute;top:0;left:0;overflow:hidden;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;transform-origin:0 0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);will-change:transform;transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1);z-index:-1}.mdl-menu__container.is-visible .mdl-menu__outline{opacity:1;-webkit-transform:scale(1);transform:scale(1);z-index:999}.mdl-menu__outline.mdl-menu--bottom-right{-webkit-transform-origin:100% 0;transform-origin:100% 0}.mdl-menu__outline.mdl-menu--top-left{-webkit-transform-origin:0 100%;transform-origin:0 100%}.mdl-menu__outline.mdl-menu--top-right{-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.mdl-menu{position:absolute;list-style:none;top:0;left:0;height:auto;width:auto;min-width:124px;padding:8px 0;margin:0;opacity:0;clip:rect(0 0 0 0);z-index:-1}.mdl-menu__container.is-visible .mdl-menu{opacity:1;z-index:999}.mdl-menu.is-animating{transition:opacity .2s cubic-bezier(.4,0,.2,1),clip .3s cubic-bezier(.4,0,.2,1)}.mdl-menu.mdl-menu--bottom-right{left:auto;right:0}.mdl-menu.mdl-menu--top-left{top:auto;bottom:0}.mdl-menu.mdl-menu--top-right{top:auto;left:auto;bottom:0;right:0}.mdl-menu.mdl-menu--unaligned{top:auto;left:auto}.mdl-menu__item{display:block;border:none;color:rgba(0,0,0,.87);background-color:transparent;text-align:left;margin:0;padding:0 16px;outline-color:#bdbdbd;position:relative;overflow:hidden;font-size:14px;font-weight:400;letter-spacing:0;text-decoration:none;cursor:pointer;height:48px;line-height:48px;white-space:nowrap;opacity:0;transition:opacity .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-menu__container.is-visible .mdl-menu__item{opacity:1}.mdl-menu__item::-moz-focus-inner{border:0}.mdl-menu__item--full-bleed-divider{border-bottom:1px solid rgba(0,0,0,.12)}.mdl-menu__item[disabled],.mdl-menu__item[data-mdl-disabled]{color:#bdbdbd;background-color:transparent;cursor:auto}.mdl-menu__item[disabled]:hover,.mdl-menu__item[data-mdl-disabled]:hover{background-color:transparent}.mdl-menu__item[disabled]:focus,.mdl-menu__item[data-mdl-disabled]:focus{background-color:transparent}.mdl-menu__item[disabled] .mdl-ripple,.mdl-menu__item[data-mdl-disabled] .mdl-ripple{background:0 0}.mdl-menu__item:hover{background-color:#eee}.mdl-menu__item:focus{outline:none;background-color:#eee}.mdl-menu__item:active{background-color:#e0e0e0}.mdl-menu__item--ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-progress{display:block;position:relative;height:4px;width:500px;max-width:100%}.mdl-progress>.bar{display:block;position:absolute;top:0;bottom:0;width:0%;transition:width .2s cubic-bezier(.4,0,.2,1)}.mdl-progress>.progressbar{background-color:rgb(63,81,181);z-index:1;left:0}.mdl-progress>.bufferbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(63,81,181),rgb(63,81,181));z-index:0;left:0}.mdl-progress>.auxbar{right:0}@supports (-webkit-appearance:none){.mdl-progress:not(.mdl-progress--indeterminate):not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate):not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(63,81,181),rgb(63,81,181));-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=")}}.mdl-progress:not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.9),rgba(255,255,255,.9)),linear-gradient(to right,rgb(63,81,181),rgb(63,81,181))}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1{-webkit-animation-name:indeterminate1;animation-name:indeterminate1}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1,.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-color:rgb(63,81,181);-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-image:none;-webkit-animation-name:indeterminate2;animation-name:indeterminate2}@-webkit-keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@-webkit-keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}@keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}.mdl-navigation{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;box-sizing:border-box}.mdl-navigation__link{color:#424242;text-decoration:none;margin:0;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0;opacity:.87}.mdl-navigation__link .material-icons{vertical-align:middle}.mdl-layout{width:100%;height:100%;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow-y:auto;overflow-x:hidden;position:relative;-webkit-overflow-scrolling:touch}.mdl-layout.is-small-screen .mdl-layout--large-screen-only{display:none}.mdl-layout:not(.is-small-screen) .mdl-layout--small-screen-only{display:none}.mdl-layout__container{position:absolute;width:100%;height:100%}.mdl-layout__title,.mdl-layout-title{display:block;position:relative;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400;box-sizing:border-box}.mdl-layout-spacer{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-layout__drawer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:240px;height:100%;max-height:100%;position:absolute;top:0;left:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);box-sizing:border-box;border-right:1px solid #e0e0e0;background:#fafafa;-webkit-transform:translateX(-250px);transform:translateX(-250px);-webkit-transform-style:preserve-3d;transform-style:preserve-3d;will-change:transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;color:#424242;overflow:visible;overflow-y:auto;z-index:5}.mdl-layout__drawer.is-visible{-webkit-transform:translateX(0);transform:translateX(0)}.mdl-layout__drawer.is-visible~.mdl-layout__content.mdl-layout__content{overflow:hidden}.mdl-layout__drawer>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:64px;padding-left:40px}@media screen and (max-width:1024px){.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:56px;padding-left:16px}}.mdl-layout__drawer .mdl-navigation{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-ms-flex-align:stretch;-ms-grid-row-align:stretch;align-items:stretch;padding-top:16px}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{display:block;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;padding:16px 40px;margin:0;color:#757575}@media screen and (max-width:1024px){.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{padding:16px}}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link:hover{background-color:#e0e0e0}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link--current{background-color:#e0e0e0;color:#000}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__drawer{-webkit-transform:translateX(0);transform:translateX(0)}}.mdl-layout__drawer-button{display:block;position:absolute;height:48px;width:48px;border:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden;text-align:center;cursor:pointer;font-size:26px;line-height:56px;font-family:Helvetica,Arial,sans-serif;margin:8px 12px;top:0;left:0;color:rgb(255,255,255);z-index:4}.mdl-layout__header .mdl-layout__drawer-button{position:absolute;color:rgb(255,255,255);background-color:inherit}@media screen and (max-width:1024px){.mdl-layout__header .mdl-layout__drawer-button{margin:4px}}@media screen and (max-width:1024px){.mdl-layout__drawer-button{margin:4px;color:rgba(0,0,0,.5)}}@media screen and (min-width:1025px){.mdl-layout__drawer-button{line-height:54px}.mdl-layout--no-desktop-drawer-button .mdl-layout__drawer-button,.mdl-layout--fixed-drawer>.mdl-layout__drawer-button,.mdl-layout--no-drawer-button .mdl-layout__drawer-button{display:none}}.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;box-sizing:border-box;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;width:100%;margin:0;padding:0;border:none;min-height:64px;max-height:1000px;z-index:3;background-color:rgb(63,81,181);color:rgb(255,255,255);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:max-height,box-shadow}@media screen and (max-width:1024px){.mdl-layout__header{min-height:56px}}.mdl-layout--fixed-drawer.is-upgraded:not(.is-small-screen)>.mdl-layout__header{margin-left:240px;width:calc(100% - 240px)}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__header .mdl-layout__header-row{padding-left:40px}}.mdl-layout__header>.mdl-layout-icon{position:absolute;left:40px;top:16px;height:32px;width:32px;overflow:hidden;z-index:3;display:block}@media screen and (max-width:1024px){.mdl-layout__header>.mdl-layout-icon{left:16px;top:12px}}.mdl-layout.has-drawer .mdl-layout__header>.mdl-layout-icon{display:none}.mdl-layout__header.is-compact{max-height:64px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact{max-height:56px}}.mdl-layout__header.is-compact.has-tabs{height:112px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact.has-tabs{min-height:104px}}@media screen and (max-width:1024px){.mdl-layout__header{display:none}.mdl-layout--fixed-header>.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex}}.mdl-layout__header--transparent.mdl-layout__header--transparent{background-color:transparent;box-shadow:none}.mdl-layout__header--seamed,.mdl-layout__header--scroll{box-shadow:none}.mdl-layout__header--waterfall{box-shadow:none;overflow:hidden}.mdl-layout__header--waterfall.is-casting-shadow{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-layout__header--waterfall.mdl-layout__header--waterfall-hide-top{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.mdl-layout__header-row{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:64px;margin:0;padding:0 40px 0 80px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:40px}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__header-row{padding-left:40px}}@media screen and (max-width:1024px){.mdl-layout__header-row{height:56px;padding:0 16px 0 72px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:16px}}.mdl-layout__header-row>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__header--scroll .mdl-layout__header-row{width:100%}.mdl-layout__header-row .mdl-navigation{margin:0;padding:0;height:64px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation{height:56px}}.mdl-layout__header-row .mdl-navigation__link{display:block;color:rgb(255,255,255);line-height:64px;padding:0 24px}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation__link{line-height:56px;padding:0 16px}}.mdl-layout__obfuscator{background-color:transparent;position:absolute;top:0;left:0;height:100%;width:100%;z-index:4;visibility:hidden;transition-property:background-color;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-layout__obfuscator.is-visible{background-color:rgba(0,0,0,.5);visibility:visible}@supports (pointer-events:auto){.mdl-layout__obfuscator{background-color:rgba(0,0,0,.5);opacity:0;transition-property:opacity;visibility:visible;pointer-events:none}.mdl-layout__obfuscator.is-visible{pointer-events:auto;opacity:1}}.mdl-layout__content{-ms-flex:0 1 auto;position:relative;display:inline-block;overflow-y:auto;overflow-x:hidden;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;z-index:1;-webkit-overflow-scrolling:touch}.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:240px}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow:visible}@media screen and (max-width:1024px){.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:0}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow-y:auto;overflow-x:hidden}}.mdl-layout__tab-bar{height:96px;margin:0;width:calc(100% - 112px);padding:0 0 0 56px;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:rgb(63,81,181);overflow-y:hidden;overflow-x:scroll}.mdl-layout__tab-bar::-webkit-scrollbar{display:none}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}}@media screen and (max-width:1024px){.mdl-layout__tab-bar{width:calc(100% - 60px);padding:0 0 0 60px}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{width:calc(100% - 8px);padding-left:4px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar{padding:0;overflow:hidden;width:100%}.mdl-layout__tab-bar-container{position:relative;height:48px;width:100%;border:none;margin:0;z-index:2;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden}.mdl-layout__container>.mdl-layout__tab-bar-container{position:absolute;top:0;left:0}.mdl-layout__tab-bar-button{display:inline-block;position:absolute;top:0;height:48px;width:56px;z-index:4;text-align:center;background-color:rgb(63,81,181);color:transparent;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button{width:16px}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button .material-icons,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button .material-icons{position:relative;left:-4px}@media screen and (max-width:1024px){.mdl-layout__tab-bar-button{width:60px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar-button{display:none}.mdl-layout__tab-bar-button .material-icons{line-height:48px}.mdl-layout__tab-bar-button.is-active{color:rgb(255,255,255)}.mdl-layout__tab-bar-left-button{left:0}.mdl-layout__tab-bar-right-button{right:0}.mdl-layout__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(255,255,255,.6);overflow:hidden}@media screen and (max-width:1024px){.mdl-layout__tab{padding:0 12px}}.mdl-layout--fixed-tabs .mdl-layout__tab{float:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;padding:0}.mdl-layout.is-upgraded .mdl-layout__tab.is-active{color:rgb(255,255,255)}.mdl-layout.is-upgraded .mdl-layout__tab.is-active::after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(224,64,251);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-layout__tab .mdl-layout__tab-ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-layout__tab .mdl-layout__tab-ripple-container .mdl-ripple{background-color:rgb(255,255,255)}.mdl-layout__tab-panel{display:block}.mdl-layout.is-upgraded .mdl-layout__tab-panel{display:none}.mdl-layout.is-upgraded .mdl-layout__tab-panel.is-active{display:block}.mdl-radio{position:relative;font-size:16px;line-height:24px;display:inline-block;box-sizing:border-box;margin:0;padding-left:0}.mdl-radio.is-upgraded{padding-left:24px}.mdl-radio__button{line-height:24px}.mdl-radio.is-upgraded .mdl-radio__button{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-radio__outer-circle{position:absolute;top:4px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;border:2px solid rgba(0,0,0,.54);border-radius:50%;z-index:2}.mdl-radio.is-checked .mdl-radio__outer-circle{border:2px solid rgb(63,81,181)}.mdl-radio__outer-circle fieldset[disabled] .mdl-radio,.mdl-radio.is-disabled .mdl-radio__outer-circle{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-radio__inner-circle{position:absolute;z-index:1;margin:0;top:8px;left:4px;box-sizing:border-box;width:8px;height:8px;cursor:pointer;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;-webkit-transform:scale3d(0,0,0);transform:scale3d(0,0,0);border-radius:50%;background:rgb(63,81,181)}.mdl-radio.is-checked .mdl-radio__inner-circle{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}fieldset[disabled] .mdl-radio .mdl-radio__inner-circle,.mdl-radio.is-disabled .mdl-radio__inner-circle{background:rgba(0,0,0,.26);cursor:auto}.mdl-radio.is-focused .mdl-radio__inner-circle{box-shadow:0 0 0 10px rgba(0,0,0,.1)}.mdl-radio__label{cursor:pointer}fieldset[disabled] .mdl-radio .mdl-radio__label,.mdl-radio.is-disabled .mdl-radio__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-radio__ripple-container{position:absolute;z-index:2;top:-9px;left:-13px;box-sizing:border-box;width:42px;height:42px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-radio__ripple-container .mdl-ripple{background:rgb(63,81,181)}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container,.mdl-radio.is-disabled .mdl-radio__ripple-container{cursor:auto}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container .mdl-ripple,.mdl-radio.is-disabled .mdl-radio__ripple-container .mdl-ripple{background:0 0}_:-ms-input-placeholder,:root .mdl-slider.mdl-slider.is-upgraded{-ms-appearance:none;height:32px;margin:0}.mdl-slider{width:calc(100% - 40px);margin:0 20px}.mdl-slider.is-upgraded{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:2px;background:0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:0;padding:0;color:rgb(63,81,181);-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;z-index:1;cursor:pointer}.mdl-slider.is-upgraded::-moz-focus-outer{border:0}.mdl-slider.is-upgraded::-ms-tooltip{display:none}.mdl-slider.is-upgraded::-webkit-slider-runnable-track{background:0 0}.mdl-slider.is-upgraded::-moz-range-track{background:0 0;border:none}.mdl-slider.is-upgraded::-ms-track{background:0 0;color:transparent;height:2px;width:100%;border:none}.mdl-slider.is-upgraded::-ms-fill-lower{padding:0;background:linear-gradient(to right,transparent,transparent 16px,rgb(63,81,181)16px,rgb(63,81,181)0)}.mdl-slider.is-upgraded::-ms-fill-upper{padding:0;background:linear-gradient(to left,transparent,transparent 16px,rgba(0,0,0,.26)16px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background:rgb(63,81,181);border:none;transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded::-moz-range-thumb{-moz-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background-image:none;background:rgb(63,81,181);border:none}.mdl-slider.is-upgraded:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(63,81,181,.26)}.mdl-slider.is-upgraded:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(63,81,181,.26)}.mdl-slider.is-upgraded:active::-webkit-slider-thumb{background-image:none;background:rgb(63,81,181);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded:active::-moz-range-thumb{background-image:none;background:rgb(63,81,181);transform:scale(1.5)}.mdl-slider.is-upgraded::-ms-thumb{width:32px;height:32px;border:none;border-radius:50%;background:rgb(63,81,181);transform:scale(.375);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgb(63,81,181)0%,rgb(63,81,181)37.5%,rgba(63,81,181,.26)37.5%,rgba(63,81,181,.26)100%);transform:scale(1)}.mdl-slider.is-upgraded:active::-ms-thumb{background:rgb(63,81,181);transform:scale(.5625)}.mdl-slider.is-upgraded.is-lowest-value::-webkit-slider-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-moz-range-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:active::-webkit-slider-thumb{border:1.6px solid rgba(0,0,0,.26);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:9px}.mdl-slider.is-upgraded.is-lowest-value:active::-moz-range-thumb{border:1.5px solid rgba(0,0,0,.26);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value::-ms-thumb{background:radial-gradient(circle closest-side,transparent 0%,transparent 66.67%,rgba(0,0,0,.26)66.67%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgba(0,0,0,.12)0%,rgba(0,0,0,.12)25%,rgba(0,0,0,.26)25%,rgba(0,0,0,.26)37.5%,rgba(0,0,0,.12)37.5%,rgba(0,0,0,.12)100%);transform:scale(1)}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-thumb{transform:scale(.5625);background:radial-gradient(circle closest-side,transparent 0%,transparent 77.78%,rgba(0,0,0,.26)77.78%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-lower{background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-fill-upper{margin-left:9px}.mdl-slider.is-upgraded:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled::-webkit-slider-thumb{-webkit-transform:scale(.667);transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded:disabled::-moz-range-thumb{transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-lower{background-color:rgba(0,0,0,.26);left:-6px}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-webkit-slider-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;-webkit-transform:scale(.667);transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-moz-range-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded:disabled:active::-ms-thumb,.mdl-slider.is-upgraded:disabled::-ms-thumb{transform:scale(.25);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-ms-thumb{transform:scale(.25);background:radial-gradient(circle closest-side,transparent 0%,transparent 50%,rgba(0,0,0,.26)50%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded:disabled::-ms-fill-lower{margin-right:6px;background:linear-gradient(to right,transparent,transparent 25px,rgba(0,0,0,.26)25px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded:disabled::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-fill-upper{margin-left:6px}.mdl-slider__ie-container{height:18px;overflow:visible;border:none;margin:none;padding:none}.mdl-slider__container{height:18px;position:relative;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.mdl-slider__container,.mdl-slider__background-flex{background:0 0;display:-webkit-flex;display:-ms-flexbox;display:flex}.mdl-slider__background-flex{position:absolute;height:2px;width:calc(100% - 52px);top:50%;left:0;margin:0 26px;overflow:hidden;border:0;padding:0;-webkit-transform:translate(0,-1px);transform:translate(0,-1px)}.mdl-slider__background-lower{background:rgb(63,81,181)}.mdl-slider__background-lower,.mdl-slider__background-upper{-webkit-flex:0;-ms-flex:0;flex:0;position:relative;border:0;padding:0}.mdl-slider__background-upper{background:rgba(0,0,0,.26);transition:left .18s cubic-bezier(.4,0,.2,1)}.mdl-snackbar{position:fixed;bottom:0;left:50%;cursor:default;background-color:#323232;z-index:3;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:"Roboto","Helvetica","Arial",sans-serif;will-change:transform;-webkit-transform:translate(0,80px);transform:translate(0,80px);transition:transform .25s cubic-bezier(.4,0,1,1);transition:transform .25s cubic-bezier(.4,0,1,1),-webkit-transform .25s cubic-bezier(.4,0,1,1);pointer-events:none}@media (max-width:479px){.mdl-snackbar{width:100%;left:0;min-height:48px;max-height:80px}}@media (min-width:480px){.mdl-snackbar{min-width:288px;max-width:568px;border-radius:2px;-webkit-transform:translate(-50%,80px);transform:translate(-50%,80px)}}.mdl-snackbar--active{-webkit-transform:translate(0,0);transform:translate(0,0);pointer-events:auto;transition:transform .25s cubic-bezier(0,0,.2,1);transition:transform .25s cubic-bezier(0,0,.2,1),-webkit-transform .25s cubic-bezier(0,0,.2,1)}@media (min-width:480px){.mdl-snackbar--active{-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}}.mdl-snackbar__text{padding:14px 12px 14px 24px;vertical-align:middle;color:#fff;float:left}.mdl-snackbar__action{background:0 0;border:none;color:rgb(224,64,251);float:right;padding:14px 24px 14px 12px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;line-height:1;letter-spacing:0;overflow:hidden;outline:none;opacity:0;pointer-events:none;cursor:pointer;text-decoration:none;text-align:center;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-snackbar__action::-moz-focus-inner{border:0}.mdl-snackbar__action:not([aria-hidden]){opacity:1;pointer-events:auto}.mdl-spinner{display:inline-block;position:relative;width:28px;height:28px}.mdl-spinner:not(.is-upgraded).is-active:after{content:"Loading..."}.mdl-spinner.is-upgraded.is-active{-webkit-animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite;animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite}@-webkit-keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.mdl-spinner__layer{position:absolute;width:100%;height:100%;opacity:0}.mdl-spinner__layer-1{border-color:#42a5f5}.mdl-spinner--single-color .mdl-spinner__layer-1{border-color:rgb(63,81,181)}.mdl-spinner.is-active .mdl-spinner__layer-1{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-2{border-color:#f44336}.mdl-spinner--single-color .mdl-spinner__layer-2{border-color:rgb(63,81,181)}.mdl-spinner.is-active .mdl-spinner__layer-2{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-3{border-color:#fdd835}.mdl-spinner--single-color .mdl-spinner__layer-3{border-color:rgb(63,81,181)}.mdl-spinner.is-active .mdl-spinner__layer-3{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-4{border-color:#4caf50}.mdl-spinner--single-color .mdl-spinner__layer-4{border-color:rgb(63,81,181)}.mdl-spinner.is-active .mdl-spinner__layer-4{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@-webkit-keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}@keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}.mdl-spinner__gap-patch{position:absolute;box-sizing:border-box;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__gap-patch .mdl-spinner__circle{width:1000%;left:-450%}.mdl-spinner__circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__circle-clipper .mdl-spinner__circle{width:200%}.mdl-spinner__circle{box-sizing:border-box;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent!important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0;left:0}.mdl-spinner__left .mdl-spinner__circle{border-right-color:transparent!important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.mdl-spinner.is-active .mdl-spinner__left .mdl-spinner__circle{-webkit-animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__right .mdl-spinner__circle{left:-100%;border-left-color:transparent!important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.mdl-spinner.is-active .mdl-spinner__right .mdl-spinner__circle{-webkit-animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}@keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}.mdl-switch{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0;overflow:visible;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-switch.is-upgraded{padding-left:28px}.mdl-switch__input{line-height:24px}.mdl-switch.is-upgraded .mdl-switch__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-switch__track{background:rgba(0,0,0,.26);position:absolute;left:0;top:5px;height:14px;width:36px;border-radius:14px;cursor:pointer}.mdl-switch.is-checked .mdl-switch__track{background:rgba(63,81,181,.5)}.mdl-switch__track fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__track{background:rgba(0,0,0,.12);cursor:auto}.mdl-switch__thumb{background:#fafafa;position:absolute;left:0;top:2px;height:20px;width:20px;border-radius:50%;cursor:pointer;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:left}.mdl-switch.is-checked .mdl-switch__thumb{background:rgb(63,81,181);left:16px;box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-switch__thumb fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__thumb{background:#bdbdbd;cursor:auto}.mdl-switch__focus-helper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-4px,-4px);transform:translate(-4px,-4px);display:inline-block;box-sizing:border-box;width:8px;height:8px;border-radius:50%;background-color:transparent}.mdl-switch.is-focused .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-switch.is-focused.is-checked .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(63,81,181,.26);background-color:rgba(63,81,181,.26)}.mdl-switch__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0;left:24px}.mdl-switch__label fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__label{color:#bdbdbd;cursor:auto}.mdl-switch__ripple-container{position:absolute;z-index:2;top:-12px;left:-14px;box-sizing:border-box;width:48px;height:48px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000);transition-duration:.4s;transition-timing-function:step-end;transition-property:left}.mdl-switch__ripple-container .mdl-ripple{background:rgb(63,81,181)}.mdl-switch__ripple-container fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__ripple-container{cursor:auto}fieldset[disabled] .mdl-switch .mdl-switch__ripple-container .mdl-ripple,.mdl-switch.is-disabled .mdl-switch__ripple-container .mdl-ripple{background:0 0}.mdl-switch.is-checked .mdl-switch__ripple-container{left:2px}.mdl-tabs{display:block;width:100%}.mdl-tabs__tab-bar{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:space-between;-ms-flex-line-pack:justify;align-content:space-between;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;height:48px;padding:0;margin:0;border-bottom:1px solid #e0e0e0}.mdl-tabs__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(0,0,0,.54);overflow:hidden}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active{color:rgba(0,0,0,.87)}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active:after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(63,81,181);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-tabs__tab .mdl-tabs__ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-tabs__tab .mdl-tabs__ripple-container .mdl-ripple{background:rgb(63,81,181)}.mdl-tabs__panel{display:block}.mdl-tabs.is-upgraded .mdl-tabs__panel{display:none}.mdl-tabs.is-upgraded .mdl-tabs__panel.is-active{display:block}@-webkit-keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}@keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}.mdl-textfield{position:relative;font-size:16px;display:inline-block;box-sizing:border-box;width:300px;max-width:100%;margin:0;padding:20px 0}.mdl-textfield .mdl-button{position:absolute;bottom:20px}.mdl-textfield--align-right{text-align:right}.mdl-textfield--full-width{width:100%}.mdl-textfield--expandable{min-width:32px;width:auto;min-height:32px}.mdl-textfield--expandable .mdl-button--icon{top:16px}.mdl-textfield__input{border:none;border-bottom:1px solid rgba(0,0,0,.12);display:block;font-size:16px;font-family:"Helvetica","Arial",sans-serif;margin:0;padding:4px 0;width:100%;background:0 0;text-align:left;color:inherit}.mdl-textfield__input[type="number"]{-moz-appearance:textfield}.mdl-textfield__input[type="number"]::-webkit-inner-spin-button,.mdl-textfield__input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.mdl-textfield.is-focused .mdl-textfield__input{outline:none}.mdl-textfield.is-invalid .mdl-textfield__input{border-color:#d50000;box-shadow:none}fieldset[disabled] .mdl-textfield .mdl-textfield__input,.mdl-textfield.is-disabled .mdl-textfield__input{background-color:transparent;border-bottom:1px dotted rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-textfield textarea.mdl-textfield__input{display:block}.mdl-textfield__label{bottom:0;color:rgba(0,0,0,.26);font-size:16px;left:0;right:0;pointer-events:none;position:absolute;display:block;top:24px;width:100%;overflow:hidden;white-space:nowrap;text-align:left}.mdl-textfield.is-dirty .mdl-textfield__label,.mdl-textfield.has-placeholder .mdl-textfield__label{visibility:hidden}.mdl-textfield--floating-label .mdl-textfield__label{transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{transition:none}fieldset[disabled] .mdl-textfield .mdl-textfield__label,.mdl-textfield.is-disabled.is-disabled .mdl-textfield__label{color:rgba(0,0,0,.26)}.mdl-textfield--floating-label.is-focused .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{color:rgb(63,81,181);font-size:12px;top:4px;visibility:visible}.mdl-textfield--floating-label.is-focused .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__expandable-holder .mdl-textfield__label{top:-16px}.mdl-textfield--floating-label.is-invalid .mdl-textfield__label{color:#d50000;font-size:12px}.mdl-textfield__label:after{background-color:rgb(63,81,181);bottom:20px;content:'';height:2px;left:45%;position:absolute;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);visibility:hidden;width:10px}.mdl-textfield.is-focused .mdl-textfield__label:after{left:0;visibility:visible;width:100%}.mdl-textfield.is-invalid .mdl-textfield__label:after{background-color:#d50000}.mdl-textfield__error{color:#d50000;position:absolute;font-size:12px;margin-top:3px;visibility:hidden;display:block}.mdl-textfield.is-invalid .mdl-textfield__error{visibility:visible}.mdl-textfield__expandable-holder{display:inline-block;position:relative;margin-left:32px;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-block;max-width:.1px}.mdl-textfield.is-focused .mdl-textfield__expandable-holder,.mdl-textfield.is-dirty .mdl-textfield__expandable-holder{max-width:600px}.mdl-textfield__expandable-holder .mdl-textfield__label:after{bottom:0}.mdl-tooltip{-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:top center;transform-origin:top center;z-index:999;background:rgba(97,97,97,.9);border-radius:2px;color:#fff;display:inline-block;font-size:10px;font-weight:500;line-height:14px;max-width:170px;position:fixed;top:-500px;left:-500px;padding:8px;text-align:center}.mdl-tooltip.is-active{-webkit-animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards;animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards}.mdl-tooltip--large{line-height:14px;font-size:14px;padding:16px}@-webkit-keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}@keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}.mdl-shadow--2dp{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-shadow--3dp{box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-shadow--4dp{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2)}.mdl-shadow--6dp{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.2)}.mdl-shadow--8dp{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.2)}.mdl-shadow--16dp{box-shadow:0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)}.mdl-shadow--24dp{box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2)}.mdl-grid{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;margin:0 auto;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.mdl-grid.mdl-grid--no-spacing{padding:0}.mdl-cell{box-sizing:border-box}.mdl-cell--top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.mdl-cell--middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-cell--bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.mdl-cell--stretch{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.mdl-grid.mdl-grid--no-spacing>.mdl-cell{margin:0}.mdl-cell--order-1{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12{-webkit-order:12;-ms-flex-order:12;order:12}@media (max-width:479px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:100%}.mdl-cell--hide-phone{display:none!important}.mdl-cell--order-1-phone.mdl-cell--order-1-phone{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-phone.mdl-cell--order-2-phone{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-phone.mdl-cell--order-3-phone{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-phone.mdl-cell--order-4-phone{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-phone.mdl-cell--order-5-phone{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-phone.mdl-cell--order-6-phone{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-phone.mdl-cell--order-7-phone{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-phone.mdl-cell--order-8-phone{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-phone.mdl-cell--order-9-phone{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-phone.mdl-cell--order-10-phone{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-phone.mdl-cell--order-11-phone{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-phone.mdl-cell--order-12-phone{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:25%}.mdl-cell--2-col,.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:50%}.mdl-cell--3-col,.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:75%}.mdl-cell--4-col,.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:100%}.mdl-cell--5-col,.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:100%}.mdl-cell--6-col,.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:100%}.mdl-cell--7-col,.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:100%}.mdl-cell--8-col,.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:25%}.mdl-cell--2-offset,.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:50%}.mdl-cell--3-offset,.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:75%}}@media (min-width:480px) and (max-width:839px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:50%}.mdl-cell--hide-tablet{display:none!important}.mdl-cell--order-1-tablet.mdl-cell--order-1-tablet{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-tablet.mdl-cell--order-2-tablet{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-tablet.mdl-cell--order-3-tablet{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-tablet.mdl-cell--order-4-tablet{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-tablet.mdl-cell--order-5-tablet{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-tablet.mdl-cell--order-6-tablet{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-tablet.mdl-cell--order-7-tablet{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-tablet.mdl-cell--order-8-tablet{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-tablet.mdl-cell--order-9-tablet{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-tablet.mdl-cell--order-10-tablet{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-tablet.mdl-cell--order-11-tablet{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-tablet.mdl-cell--order-12-tablet{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:calc(12.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:12.5%}.mdl-cell--2-col,.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:25%}.mdl-cell--3-col,.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:calc(37.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:37.5%}.mdl-cell--4-col,.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:50%}.mdl-cell--5-col,.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:calc(62.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:62.5%}.mdl-cell--6-col,.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:75%}.mdl-cell--7-col,.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:calc(87.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:87.5%}.mdl-cell--8-col,.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:calc(12.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:12.5%}.mdl-cell--2-offset,.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:25%}.mdl-cell--3-offset,.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:calc(37.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:37.5%}.mdl-cell--4-offset,.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:50%}.mdl-cell--5-offset,.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:calc(62.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:62.5%}.mdl-cell--6-offset,.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:75%}.mdl-cell--7-offset,.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:calc(87.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:87.5%}}@media (min-width:840px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:33.3333333333%}.mdl-cell--hide-desktop{display:none!important}.mdl-cell--order-1-desktop.mdl-cell--order-1-desktop{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-desktop.mdl-cell--order-2-desktop{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-desktop.mdl-cell--order-3-desktop{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-desktop.mdl-cell--order-4-desktop{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-desktop.mdl-cell--order-5-desktop{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-desktop.mdl-cell--order-6-desktop{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-desktop.mdl-cell--order-7-desktop{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-desktop.mdl-cell--order-8-desktop{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-desktop.mdl-cell--order-9-desktop{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-desktop.mdl-cell--order-10-desktop{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-desktop.mdl-cell--order-11-desktop{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-desktop.mdl-cell--order-12-desktop{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:calc(8.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:8.3333333333%}.mdl-cell--2-col,.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:calc(16.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:16.6666666667%}.mdl-cell--3-col,.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:25%}.mdl-cell--4-col,.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:33.3333333333%}.mdl-cell--5-col,.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:calc(41.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:41.6666666667%}.mdl-cell--6-col,.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:50%}.mdl-cell--7-col,.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:calc(58.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:58.3333333333%}.mdl-cell--8-col,.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:calc(66.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:66.6666666667%}.mdl-cell--9-col,.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:75%}.mdl-cell--10-col,.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:calc(83.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:83.3333333333%}.mdl-cell--11-col,.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:calc(91.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:91.6666666667%}.mdl-cell--12-col,.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:calc(8.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:8.3333333333%}.mdl-cell--2-offset,.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:calc(16.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:16.6666666667%}.mdl-cell--3-offset,.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:25%}.mdl-cell--4-offset,.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:calc(33.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:33.3333333333%}.mdl-cell--5-offset,.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:calc(41.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:41.6666666667%}.mdl-cell--6-offset,.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:50%}.mdl-cell--7-offset,.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:calc(58.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:58.3333333333%}.mdl-cell--8-offset,.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:calc(66.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:66.6666666667%}.mdl-cell--9-offset,.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:75%}.mdl-cell--10-offset,.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:calc(83.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:83.3333333333%}.mdl-cell--11-offset,.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:calc(91.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:91.6666666667%}}body{margin:0}.styleguide-demo h1{margin:48px 24px 0}.styleguide-demo h1:after{content:'';display:block;width:100%;border-bottom:1px solid rgba(0,0,0,.5);margin-top:24px}.styleguide-demo{opacity:0;transition:opacity .6s ease}.styleguide-masthead{height:256px;background:#212121;padding:115px 16px 0}.styleguide-container{position:relative;max-width:960px;width:100%}.styleguide-title{color:#fff;bottom:auto;position:relative;font-size:56px;font-weight:300;line-height:1;letter-spacing:-.02em}.styleguide-title:after{border-bottom:0}.styleguide-title span{font-weight:300}.mdl-styleguide .mdl-layout__drawer .mdl-navigation__link{padding:10px 24px}.demosLoaded .styleguide-demo{opacity:1}iframe{display:block;width:100%;border:none}iframe.heightSet{overflow:hidden}.demo-wrapper{margin:24px}.demo-wrapper iframe{border:1px solid rgba(0,0,0,.5)} \ No newline at end of file diff --git a/modules/material/www/material.orange-light_blue.1.2.1.min.css b/modules/material/www/material.orange-light_blue.1.2.1.min.css new file mode 100644 index 00000000..b45f502d --- /dev/null +++ b/modules/material/www/material.orange-light_blue.1.2.1.min.css @@ -0,0 +1,8 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.2.1 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +@charset "UTF-8";html{color:rgba(0,0,0,.87)}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.browserupgrade{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.hidden{display:none!important}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}@media print{*,*:before,*:after,*:first-letter{background:transparent!important;color:#000!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}a,.mdl-accordion,.mdl-button,.mdl-card,.mdl-checkbox,.mdl-dropdown-menu,.mdl-icon-toggle,.mdl-item,.mdl-radio,.mdl-slider,.mdl-switch,.mdl-tabs__tab{-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:rgba(255,255,255,0)}html{width:100%;height:100%;-ms-touch-action:manipulation;touch-action:manipulation}body{width:100%;min-height:100%}main{display:block}*[hidden]{display:none!important}html,body{font-family:"Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:20px}h1,h2,h3,h4,h5,h6,p{padding:0}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400;line-height:1.35;letter-spacing:-.02em;opacity:.54;font-size:.6em}h1{font-size:56px;line-height:1.35;letter-spacing:-.02em;margin:24px 0}h1,h2{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h2{font-size:45px;line-height:48px}h2,h3{margin:24px 0}h3{font-size:34px;line-height:40px}h3,h4{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h4{font-size:24px;line-height:32px;-moz-osx-font-smoothing:grayscale;margin:24px 0 16px}h5{font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;margin:24px 0 16px}h6{font-size:16px;letter-spacing:.04em}h6,p{font-weight:400;line-height:24px}p{font-size:14px;letter-spacing:0;margin:0 0 16px}a{color:rgb(64,196,255);font-weight:500}blockquote{font-family:"Roboto","Helvetica","Arial",sans-serif;position:relative;font-size:24px;font-weight:300;font-style:italic;line-height:1.35;letter-spacing:.08em}blockquote:before{position:absolute;left:-.5em;content:'“'}blockquote:after{content:'”';margin-left:-.05em}mark{background-color:#f4ff81}dt{font-weight:700}address{font-size:12px;line-height:1;font-style:normal}address,ul,ol{font-weight:400;letter-spacing:0}ul,ol{font-size:14px;line-height:24px}.mdl-typography--display-4,.mdl-typography--display-4-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:112px;font-weight:300;line-height:1;letter-spacing:-.04em}.mdl-typography--display-4-color-contrast{opacity:.54}.mdl-typography--display-3,.mdl-typography--display-3-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:56px;font-weight:400;line-height:1.35;letter-spacing:-.02em}.mdl-typography--display-3-color-contrast{opacity:.54}.mdl-typography--display-2,.mdl-typography--display-2-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:45px;font-weight:400;line-height:48px}.mdl-typography--display-2-color-contrast{opacity:.54}.mdl-typography--display-1,.mdl-typography--display-1-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:34px;font-weight:400;line-height:40px}.mdl-typography--display-1-color-contrast{opacity:.54}.mdl-typography--headline,.mdl-typography--headline-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:24px;font-weight:400;line-height:32px;-moz-osx-font-smoothing:grayscale}.mdl-typography--headline-color-contrast{opacity:.87}.mdl-typography--title,.mdl-typography--title-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}.mdl-typography--title-color-contrast{opacity:.87}.mdl-typography--subhead,.mdl-typography--subhead-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;line-height:24px;letter-spacing:.04em}.mdl-typography--subhead-color-contrast{opacity:.87}.mdl-typography--body-2,.mdl-typography--body-2-color-contrast{font-size:14px;font-weight:700;line-height:24px;letter-spacing:0}.mdl-typography--body-2-color-contrast{opacity:.87}.mdl-typography--body-1,.mdl-typography--body-1-color-contrast{font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-color-contrast{opacity:.87}.mdl-typography--body-2-force-preferred-font,.mdl-typography--body-2-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:24px;letter-spacing:0}.mdl-typography--body-2-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--body-1-force-preferred-font,.mdl-typography--body-1-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--caption,.mdl-typography--caption-force-preferred-font{font-size:12px;font-weight:400;line-height:1;letter-spacing:0}.mdl-typography--caption-force-preferred-font{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--caption-color-contrast,.mdl-typography--caption-force-preferred-font-color-contrast{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;opacity:.54}.mdl-typography--caption-force-preferred-font-color-contrast,.mdl-typography--menu{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--menu{font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--menu-color-contrast{opacity:.87}.mdl-typography--menu-color-contrast,.mdl-typography--button,.mdl-typography--button-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--button,.mdl-typography--button-color-contrast{text-transform:uppercase}.mdl-typography--button-color-contrast{opacity:.87}.mdl-typography--text-left{text-align:left}.mdl-typography--text-right{text-align:right}.mdl-typography--text-center{text-align:center}.mdl-typography--text-justify{text-align:justify}.mdl-typography--text-nowrap{white-space:nowrap}.mdl-typography--text-lowercase{text-transform:lowercase}.mdl-typography--text-uppercase{text-transform:uppercase}.mdl-typography--text-capitalize{text-transform:capitalize}.mdl-typography--font-thin{font-weight:200!important}.mdl-typography--font-light{font-weight:300!important}.mdl-typography--font-regular{font-weight:400!important}.mdl-typography--font-medium{font-weight:500!important}.mdl-typography--font-bold{font-weight:700!important}.mdl-typography--font-black{font-weight:900!important}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased}.mdl-color-text--red{color:#f44336 !important}.mdl-color--red{background-color:#f44336 !important}.mdl-color-text--red-50{color:#ffebee !important}.mdl-color--red-50{background-color:#ffebee !important}.mdl-color-text--red-100{color:#ffcdd2 !important}.mdl-color--red-100{background-color:#ffcdd2 !important}.mdl-color-text--red-200{color:#ef9a9a !important}.mdl-color--red-200{background-color:#ef9a9a !important}.mdl-color-text--red-300{color:#e57373 !important}.mdl-color--red-300{background-color:#e57373 !important}.mdl-color-text--red-400{color:#ef5350 !important}.mdl-color--red-400{background-color:#ef5350 !important}.mdl-color-text--red-500{color:#f44336 !important}.mdl-color--red-500{background-color:#f44336 !important}.mdl-color-text--red-600{color:#e53935 !important}.mdl-color--red-600{background-color:#e53935 !important}.mdl-color-text--red-700{color:#d32f2f !important}.mdl-color--red-700{background-color:#d32f2f !important}.mdl-color-text--red-800{color:#c62828 !important}.mdl-color--red-800{background-color:#c62828 !important}.mdl-color-text--red-900{color:#b71c1c !important}.mdl-color--red-900{background-color:#b71c1c !important}.mdl-color-text--red-A100{color:#ff8a80 !important}.mdl-color--red-A100{background-color:#ff8a80 !important}.mdl-color-text--red-A200{color:#ff5252 !important}.mdl-color--red-A200{background-color:#ff5252 !important}.mdl-color-text--red-A400{color:#ff1744 !important}.mdl-color--red-A400{background-color:#ff1744 !important}.mdl-color-text--red-A700{color:#d50000 !important}.mdl-color--red-A700{background-color:#d50000 !important}.mdl-color-text--pink{color:#e91e63 !important}.mdl-color--pink{background-color:#e91e63 !important}.mdl-color-text--pink-50{color:#fce4ec !important}.mdl-color--pink-50{background-color:#fce4ec !important}.mdl-color-text--pink-100{color:#f8bbd0 !important}.mdl-color--pink-100{background-color:#f8bbd0 !important}.mdl-color-text--pink-200{color:#f48fb1 !important}.mdl-color--pink-200{background-color:#f48fb1 !important}.mdl-color-text--pink-300{color:#f06292 !important}.mdl-color--pink-300{background-color:#f06292 !important}.mdl-color-text--pink-400{color:#ec407a !important}.mdl-color--pink-400{background-color:#ec407a !important}.mdl-color-text--pink-500{color:#e91e63 !important}.mdl-color--pink-500{background-color:#e91e63 !important}.mdl-color-text--pink-600{color:#d81b60 !important}.mdl-color--pink-600{background-color:#d81b60 !important}.mdl-color-text--pink-700{color:#c2185b !important}.mdl-color--pink-700{background-color:#c2185b !important}.mdl-color-text--pink-800{color:#ad1457 !important}.mdl-color--pink-800{background-color:#ad1457 !important}.mdl-color-text--pink-900{color:#880e4f !important}.mdl-color--pink-900{background-color:#880e4f !important}.mdl-color-text--pink-A100{color:#ff80ab !important}.mdl-color--pink-A100{background-color:#ff80ab !important}.mdl-color-text--pink-A200{color:#ff4081 !important}.mdl-color--pink-A200{background-color:#ff4081 !important}.mdl-color-text--pink-A400{color:#f50057 !important}.mdl-color--pink-A400{background-color:#f50057 !important}.mdl-color-text--pink-A700{color:#c51162 !important}.mdl-color--pink-A700{background-color:#c51162 !important}.mdl-color-text--purple{color:#9c27b0 !important}.mdl-color--purple{background-color:#9c27b0 !important}.mdl-color-text--purple-50{color:#f3e5f5 !important}.mdl-color--purple-50{background-color:#f3e5f5 !important}.mdl-color-text--purple-100{color:#e1bee7 !important}.mdl-color--purple-100{background-color:#e1bee7 !important}.mdl-color-text--purple-200{color:#ce93d8 !important}.mdl-color--purple-200{background-color:#ce93d8 !important}.mdl-color-text--purple-300{color:#ba68c8 !important}.mdl-color--purple-300{background-color:#ba68c8 !important}.mdl-color-text--purple-400{color:#ab47bc !important}.mdl-color--purple-400{background-color:#ab47bc !important}.mdl-color-text--purple-500{color:#9c27b0 !important}.mdl-color--purple-500{background-color:#9c27b0 !important}.mdl-color-text--purple-600{color:#8e24aa !important}.mdl-color--purple-600{background-color:#8e24aa !important}.mdl-color-text--purple-700{color:#7b1fa2 !important}.mdl-color--purple-700{background-color:#7b1fa2 !important}.mdl-color-text--purple-800{color:#6a1b9a !important}.mdl-color--purple-800{background-color:#6a1b9a !important}.mdl-color-text--purple-900{color:#4a148c !important}.mdl-color--purple-900{background-color:#4a148c !important}.mdl-color-text--purple-A100{color:#ea80fc !important}.mdl-color--purple-A100{background-color:#ea80fc !important}.mdl-color-text--purple-A200{color:#e040fb !important}.mdl-color--purple-A200{background-color:#e040fb !important}.mdl-color-text--purple-A400{color:#d500f9 !important}.mdl-color--purple-A400{background-color:#d500f9 !important}.mdl-color-text--purple-A700{color:#a0f !important}.mdl-color--purple-A700{background-color:#a0f !important}.mdl-color-text--deep-purple{color:#673ab7 !important}.mdl-color--deep-purple{background-color:#673ab7 !important}.mdl-color-text--deep-purple-50{color:#ede7f6 !important}.mdl-color--deep-purple-50{background-color:#ede7f6 !important}.mdl-color-text--deep-purple-100{color:#d1c4e9 !important}.mdl-color--deep-purple-100{background-color:#d1c4e9 !important}.mdl-color-text--deep-purple-200{color:#b39ddb !important}.mdl-color--deep-purple-200{background-color:#b39ddb !important}.mdl-color-text--deep-purple-300{color:#9575cd !important}.mdl-color--deep-purple-300{background-color:#9575cd !important}.mdl-color-text--deep-purple-400{color:#7e57c2 !important}.mdl-color--deep-purple-400{background-color:#7e57c2 !important}.mdl-color-text--deep-purple-500{color:#673ab7 !important}.mdl-color--deep-purple-500{background-color:#673ab7 !important}.mdl-color-text--deep-purple-600{color:#5e35b1 !important}.mdl-color--deep-purple-600{background-color:#5e35b1 !important}.mdl-color-text--deep-purple-700{color:#512da8 !important}.mdl-color--deep-purple-700{background-color:#512da8 !important}.mdl-color-text--deep-purple-800{color:#4527a0 !important}.mdl-color--deep-purple-800{background-color:#4527a0 !important}.mdl-color-text--deep-purple-900{color:#311b92 !important}.mdl-color--deep-purple-900{background-color:#311b92 !important}.mdl-color-text--deep-purple-A100{color:#b388ff !important}.mdl-color--deep-purple-A100{background-color:#b388ff !important}.mdl-color-text--deep-purple-A200{color:#7c4dff !important}.mdl-color--deep-purple-A200{background-color:#7c4dff !important}.mdl-color-text--deep-purple-A400{color:#651fff !important}.mdl-color--deep-purple-A400{background-color:#651fff !important}.mdl-color-text--deep-purple-A700{color:#6200ea !important}.mdl-color--deep-purple-A700{background-color:#6200ea !important}.mdl-color-text--indigo{color:#3f51b5 !important}.mdl-color--indigo{background-color:#3f51b5 !important}.mdl-color-text--indigo-50{color:#e8eaf6 !important}.mdl-color--indigo-50{background-color:#e8eaf6 !important}.mdl-color-text--indigo-100{color:#c5cae9 !important}.mdl-color--indigo-100{background-color:#c5cae9 !important}.mdl-color-text--indigo-200{color:#9fa8da !important}.mdl-color--indigo-200{background-color:#9fa8da !important}.mdl-color-text--indigo-300{color:#7986cb !important}.mdl-color--indigo-300{background-color:#7986cb !important}.mdl-color-text--indigo-400{color:#5c6bc0 !important}.mdl-color--indigo-400{background-color:#5c6bc0 !important}.mdl-color-text--indigo-500{color:#3f51b5 !important}.mdl-color--indigo-500{background-color:#3f51b5 !important}.mdl-color-text--indigo-600{color:#3949ab !important}.mdl-color--indigo-600{background-color:#3949ab !important}.mdl-color-text--indigo-700{color:#303f9f !important}.mdl-color--indigo-700{background-color:#303f9f !important}.mdl-color-text--indigo-800{color:#283593 !important}.mdl-color--indigo-800{background-color:#283593 !important}.mdl-color-text--indigo-900{color:#1a237e !important}.mdl-color--indigo-900{background-color:#1a237e !important}.mdl-color-text--indigo-A100{color:#8c9eff !important}.mdl-color--indigo-A100{background-color:#8c9eff !important}.mdl-color-text--indigo-A200{color:#536dfe !important}.mdl-color--indigo-A200{background-color:#536dfe !important}.mdl-color-text--indigo-A400{color:#3d5afe !important}.mdl-color--indigo-A400{background-color:#3d5afe !important}.mdl-color-text--indigo-A700{color:#304ffe !important}.mdl-color--indigo-A700{background-color:#304ffe !important}.mdl-color-text--blue{color:#2196f3 !important}.mdl-color--blue{background-color:#2196f3 !important}.mdl-color-text--blue-50{color:#e3f2fd !important}.mdl-color--blue-50{background-color:#e3f2fd !important}.mdl-color-text--blue-100{color:#bbdefb !important}.mdl-color--blue-100{background-color:#bbdefb !important}.mdl-color-text--blue-200{color:#90caf9 !important}.mdl-color--blue-200{background-color:#90caf9 !important}.mdl-color-text--blue-300{color:#64b5f6 !important}.mdl-color--blue-300{background-color:#64b5f6 !important}.mdl-color-text--blue-400{color:#42a5f5 !important}.mdl-color--blue-400{background-color:#42a5f5 !important}.mdl-color-text--blue-500{color:#2196f3 !important}.mdl-color--blue-500{background-color:#2196f3 !important}.mdl-color-text--blue-600{color:#1e88e5 !important}.mdl-color--blue-600{background-color:#1e88e5 !important}.mdl-color-text--blue-700{color:#1976d2 !important}.mdl-color--blue-700{background-color:#1976d2 !important}.mdl-color-text--blue-800{color:#1565c0 !important}.mdl-color--blue-800{background-color:#1565c0 !important}.mdl-color-text--blue-900{color:#0d47a1 !important}.mdl-color--blue-900{background-color:#0d47a1 !important}.mdl-color-text--blue-A100{color:#82b1ff !important}.mdl-color--blue-A100{background-color:#82b1ff !important}.mdl-color-text--blue-A200{color:#448aff !important}.mdl-color--blue-A200{background-color:#448aff !important}.mdl-color-text--blue-A400{color:#2979ff !important}.mdl-color--blue-A400{background-color:#2979ff !important}.mdl-color-text--blue-A700{color:#2962ff !important}.mdl-color--blue-A700{background-color:#2962ff !important}.mdl-color-text--light-blue{color:#03a9f4 !important}.mdl-color--light-blue{background-color:#03a9f4 !important}.mdl-color-text--light-blue-50{color:#e1f5fe !important}.mdl-color--light-blue-50{background-color:#e1f5fe !important}.mdl-color-text--light-blue-100{color:#b3e5fc !important}.mdl-color--light-blue-100{background-color:#b3e5fc !important}.mdl-color-text--light-blue-200{color:#81d4fa !important}.mdl-color--light-blue-200{background-color:#81d4fa !important}.mdl-color-text--light-blue-300{color:#4fc3f7 !important}.mdl-color--light-blue-300{background-color:#4fc3f7 !important}.mdl-color-text--light-blue-400{color:#29b6f6 !important}.mdl-color--light-blue-400{background-color:#29b6f6 !important}.mdl-color-text--light-blue-500{color:#03a9f4 !important}.mdl-color--light-blue-500{background-color:#03a9f4 !important}.mdl-color-text--light-blue-600{color:#039be5 !important}.mdl-color--light-blue-600{background-color:#039be5 !important}.mdl-color-text--light-blue-700{color:#0288d1 !important}.mdl-color--light-blue-700{background-color:#0288d1 !important}.mdl-color-text--light-blue-800{color:#0277bd !important}.mdl-color--light-blue-800{background-color:#0277bd !important}.mdl-color-text--light-blue-900{color:#01579b !important}.mdl-color--light-blue-900{background-color:#01579b !important}.mdl-color-text--light-blue-A100{color:#80d8ff !important}.mdl-color--light-blue-A100{background-color:#80d8ff !important}.mdl-color-text--light-blue-A200{color:#40c4ff !important}.mdl-color--light-blue-A200{background-color:#40c4ff !important}.mdl-color-text--light-blue-A400{color:#00b0ff !important}.mdl-color--light-blue-A400{background-color:#00b0ff !important}.mdl-color-text--light-blue-A700{color:#0091ea !important}.mdl-color--light-blue-A700{background-color:#0091ea !important}.mdl-color-text--cyan{color:#00bcd4 !important}.mdl-color--cyan{background-color:#00bcd4 !important}.mdl-color-text--cyan-50{color:#e0f7fa !important}.mdl-color--cyan-50{background-color:#e0f7fa !important}.mdl-color-text--cyan-100{color:#b2ebf2 !important}.mdl-color--cyan-100{background-color:#b2ebf2 !important}.mdl-color-text--cyan-200{color:#80deea !important}.mdl-color--cyan-200{background-color:#80deea !important}.mdl-color-text--cyan-300{color:#4dd0e1 !important}.mdl-color--cyan-300{background-color:#4dd0e1 !important}.mdl-color-text--cyan-400{color:#26c6da !important}.mdl-color--cyan-400{background-color:#26c6da !important}.mdl-color-text--cyan-500{color:#00bcd4 !important}.mdl-color--cyan-500{background-color:#00bcd4 !important}.mdl-color-text--cyan-600{color:#00acc1 !important}.mdl-color--cyan-600{background-color:#00acc1 !important}.mdl-color-text--cyan-700{color:#0097a7 !important}.mdl-color--cyan-700{background-color:#0097a7 !important}.mdl-color-text--cyan-800{color:#00838f !important}.mdl-color--cyan-800{background-color:#00838f !important}.mdl-color-text--cyan-900{color:#006064 !important}.mdl-color--cyan-900{background-color:#006064 !important}.mdl-color-text--cyan-A100{color:#84ffff !important}.mdl-color--cyan-A100{background-color:#84ffff !important}.mdl-color-text--cyan-A200{color:#18ffff !important}.mdl-color--cyan-A200{background-color:#18ffff !important}.mdl-color-text--cyan-A400{color:#00e5ff !important}.mdl-color--cyan-A400{background-color:#00e5ff !important}.mdl-color-text--cyan-A700{color:#00b8d4 !important}.mdl-color--cyan-A700{background-color:#00b8d4 !important}.mdl-color-text--teal{color:#009688 !important}.mdl-color--teal{background-color:#009688 !important}.mdl-color-text--teal-50{color:#e0f2f1 !important}.mdl-color--teal-50{background-color:#e0f2f1 !important}.mdl-color-text--teal-100{color:#b2dfdb !important}.mdl-color--teal-100{background-color:#b2dfdb !important}.mdl-color-text--teal-200{color:#80cbc4 !important}.mdl-color--teal-200{background-color:#80cbc4 !important}.mdl-color-text--teal-300{color:#4db6ac !important}.mdl-color--teal-300{background-color:#4db6ac !important}.mdl-color-text--teal-400{color:#26a69a !important}.mdl-color--teal-400{background-color:#26a69a !important}.mdl-color-text--teal-500{color:#009688 !important}.mdl-color--teal-500{background-color:#009688 !important}.mdl-color-text--teal-600{color:#00897b !important}.mdl-color--teal-600{background-color:#00897b !important}.mdl-color-text--teal-700{color:#00796b !important}.mdl-color--teal-700{background-color:#00796b !important}.mdl-color-text--teal-800{color:#00695c !important}.mdl-color--teal-800{background-color:#00695c !important}.mdl-color-text--teal-900{color:#004d40 !important}.mdl-color--teal-900{background-color:#004d40 !important}.mdl-color-text--teal-A100{color:#a7ffeb !important}.mdl-color--teal-A100{background-color:#a7ffeb !important}.mdl-color-text--teal-A200{color:#64ffda !important}.mdl-color--teal-A200{background-color:#64ffda !important}.mdl-color-text--teal-A400{color:#1de9b6 !important}.mdl-color--teal-A400{background-color:#1de9b6 !important}.mdl-color-text--teal-A700{color:#00bfa5 !important}.mdl-color--teal-A700{background-color:#00bfa5 !important}.mdl-color-text--green{color:#4caf50 !important}.mdl-color--green{background-color:#4caf50 !important}.mdl-color-text--green-50{color:#e8f5e9 !important}.mdl-color--green-50{background-color:#e8f5e9 !important}.mdl-color-text--green-100{color:#c8e6c9 !important}.mdl-color--green-100{background-color:#c8e6c9 !important}.mdl-color-text--green-200{color:#a5d6a7 !important}.mdl-color--green-200{background-color:#a5d6a7 !important}.mdl-color-text--green-300{color:#81c784 !important}.mdl-color--green-300{background-color:#81c784 !important}.mdl-color-text--green-400{color:#66bb6a !important}.mdl-color--green-400{background-color:#66bb6a !important}.mdl-color-text--green-500{color:#4caf50 !important}.mdl-color--green-500{background-color:#4caf50 !important}.mdl-color-text--green-600{color:#43a047 !important}.mdl-color--green-600{background-color:#43a047 !important}.mdl-color-text--green-700{color:#388e3c !important}.mdl-color--green-700{background-color:#388e3c !important}.mdl-color-text--green-800{color:#2e7d32 !important}.mdl-color--green-800{background-color:#2e7d32 !important}.mdl-color-text--green-900{color:#1b5e20 !important}.mdl-color--green-900{background-color:#1b5e20 !important}.mdl-color-text--green-A100{color:#b9f6ca !important}.mdl-color--green-A100{background-color:#b9f6ca !important}.mdl-color-text--green-A200{color:#69f0ae !important}.mdl-color--green-A200{background-color:#69f0ae !important}.mdl-color-text--green-A400{color:#00e676 !important}.mdl-color--green-A400{background-color:#00e676 !important}.mdl-color-text--green-A700{color:#00c853 !important}.mdl-color--green-A700{background-color:#00c853 !important}.mdl-color-text--light-green{color:#8bc34a !important}.mdl-color--light-green{background-color:#8bc34a !important}.mdl-color-text--light-green-50{color:#f1f8e9 !important}.mdl-color--light-green-50{background-color:#f1f8e9 !important}.mdl-color-text--light-green-100{color:#dcedc8 !important}.mdl-color--light-green-100{background-color:#dcedc8 !important}.mdl-color-text--light-green-200{color:#c5e1a5 !important}.mdl-color--light-green-200{background-color:#c5e1a5 !important}.mdl-color-text--light-green-300{color:#aed581 !important}.mdl-color--light-green-300{background-color:#aed581 !important}.mdl-color-text--light-green-400{color:#9ccc65 !important}.mdl-color--light-green-400{background-color:#9ccc65 !important}.mdl-color-text--light-green-500{color:#8bc34a !important}.mdl-color--light-green-500{background-color:#8bc34a !important}.mdl-color-text--light-green-600{color:#7cb342 !important}.mdl-color--light-green-600{background-color:#7cb342 !important}.mdl-color-text--light-green-700{color:#689f38 !important}.mdl-color--light-green-700{background-color:#689f38 !important}.mdl-color-text--light-green-800{color:#558b2f !important}.mdl-color--light-green-800{background-color:#558b2f !important}.mdl-color-text--light-green-900{color:#33691e !important}.mdl-color--light-green-900{background-color:#33691e !important}.mdl-color-text--light-green-A100{color:#ccff90 !important}.mdl-color--light-green-A100{background-color:#ccff90 !important}.mdl-color-text--light-green-A200{color:#b2ff59 !important}.mdl-color--light-green-A200{background-color:#b2ff59 !important}.mdl-color-text--light-green-A400{color:#76ff03 !important}.mdl-color--light-green-A400{background-color:#76ff03 !important}.mdl-color-text--light-green-A700{color:#64dd17 !important}.mdl-color--light-green-A700{background-color:#64dd17 !important}.mdl-color-text--lime{color:#cddc39 !important}.mdl-color--lime{background-color:#cddc39 !important}.mdl-color-text--lime-50{color:#f9fbe7 !important}.mdl-color--lime-50{background-color:#f9fbe7 !important}.mdl-color-text--lime-100{color:#f0f4c3 !important}.mdl-color--lime-100{background-color:#f0f4c3 !important}.mdl-color-text--lime-200{color:#e6ee9c !important}.mdl-color--lime-200{background-color:#e6ee9c !important}.mdl-color-text--lime-300{color:#dce775 !important}.mdl-color--lime-300{background-color:#dce775 !important}.mdl-color-text--lime-400{color:#d4e157 !important}.mdl-color--lime-400{background-color:#d4e157 !important}.mdl-color-text--lime-500{color:#cddc39 !important}.mdl-color--lime-500{background-color:#cddc39 !important}.mdl-color-text--lime-600{color:#c0ca33 !important}.mdl-color--lime-600{background-color:#c0ca33 !important}.mdl-color-text--lime-700{color:#afb42b !important}.mdl-color--lime-700{background-color:#afb42b !important}.mdl-color-text--lime-800{color:#9e9d24 !important}.mdl-color--lime-800{background-color:#9e9d24 !important}.mdl-color-text--lime-900{color:#827717 !important}.mdl-color--lime-900{background-color:#827717 !important}.mdl-color-text--lime-A100{color:#f4ff81 !important}.mdl-color--lime-A100{background-color:#f4ff81 !important}.mdl-color-text--lime-A200{color:#eeff41 !important}.mdl-color--lime-A200{background-color:#eeff41 !important}.mdl-color-text--lime-A400{color:#c6ff00 !important}.mdl-color--lime-A400{background-color:#c6ff00 !important}.mdl-color-text--lime-A700{color:#aeea00 !important}.mdl-color--lime-A700{background-color:#aeea00 !important}.mdl-color-text--yellow{color:#ffeb3b !important}.mdl-color--yellow{background-color:#ffeb3b !important}.mdl-color-text--yellow-50{color:#fffde7 !important}.mdl-color--yellow-50{background-color:#fffde7 !important}.mdl-color-text--yellow-100{color:#fff9c4 !important}.mdl-color--yellow-100{background-color:#fff9c4 !important}.mdl-color-text--yellow-200{color:#fff59d !important}.mdl-color--yellow-200{background-color:#fff59d !important}.mdl-color-text--yellow-300{color:#fff176 !important}.mdl-color--yellow-300{background-color:#fff176 !important}.mdl-color-text--yellow-400{color:#ffee58 !important}.mdl-color--yellow-400{background-color:#ffee58 !important}.mdl-color-text--yellow-500{color:#ffeb3b !important}.mdl-color--yellow-500{background-color:#ffeb3b !important}.mdl-color-text--yellow-600{color:#fdd835 !important}.mdl-color--yellow-600{background-color:#fdd835 !important}.mdl-color-text--yellow-700{color:#fbc02d !important}.mdl-color--yellow-700{background-color:#fbc02d !important}.mdl-color-text--yellow-800{color:#f9a825 !important}.mdl-color--yellow-800{background-color:#f9a825 !important}.mdl-color-text--yellow-900{color:#f57f17 !important}.mdl-color--yellow-900{background-color:#f57f17 !important}.mdl-color-text--yellow-A100{color:#ffff8d !important}.mdl-color--yellow-A100{background-color:#ffff8d !important}.mdl-color-text--yellow-A200{color:#ff0 !important}.mdl-color--yellow-A200{background-color:#ff0 !important}.mdl-color-text--yellow-A400{color:#ffea00 !important}.mdl-color--yellow-A400{background-color:#ffea00 !important}.mdl-color-text--yellow-A700{color:#ffd600 !important}.mdl-color--yellow-A700{background-color:#ffd600 !important}.mdl-color-text--amber{color:#ffc107 !important}.mdl-color--amber{background-color:#ffc107 !important}.mdl-color-text--amber-50{color:#fff8e1 !important}.mdl-color--amber-50{background-color:#fff8e1 !important}.mdl-color-text--amber-100{color:#ffecb3 !important}.mdl-color--amber-100{background-color:#ffecb3 !important}.mdl-color-text--amber-200{color:#ffe082 !important}.mdl-color--amber-200{background-color:#ffe082 !important}.mdl-color-text--amber-300{color:#ffd54f !important}.mdl-color--amber-300{background-color:#ffd54f !important}.mdl-color-text--amber-400{color:#ffca28 !important}.mdl-color--amber-400{background-color:#ffca28 !important}.mdl-color-text--amber-500{color:#ffc107 !important}.mdl-color--amber-500{background-color:#ffc107 !important}.mdl-color-text--amber-600{color:#ffb300 !important}.mdl-color--amber-600{background-color:#ffb300 !important}.mdl-color-text--amber-700{color:#ffa000 !important}.mdl-color--amber-700{background-color:#ffa000 !important}.mdl-color-text--amber-800{color:#ff8f00 !important}.mdl-color--amber-800{background-color:#ff8f00 !important}.mdl-color-text--amber-900{color:#ff6f00 !important}.mdl-color--amber-900{background-color:#ff6f00 !important}.mdl-color-text--amber-A100{color:#ffe57f !important}.mdl-color--amber-A100{background-color:#ffe57f !important}.mdl-color-text--amber-A200{color:#ffd740 !important}.mdl-color--amber-A200{background-color:#ffd740 !important}.mdl-color-text--amber-A400{color:#ffc400 !important}.mdl-color--amber-A400{background-color:#ffc400 !important}.mdl-color-text--amber-A700{color:#ffab00 !important}.mdl-color--amber-A700{background-color:#ffab00 !important}.mdl-color-text--orange{color:#ff9800 !important}.mdl-color--orange{background-color:#ff9800 !important}.mdl-color-text--orange-50{color:#fff3e0 !important}.mdl-color--orange-50{background-color:#fff3e0 !important}.mdl-color-text--orange-100{color:#ffe0b2 !important}.mdl-color--orange-100{background-color:#ffe0b2 !important}.mdl-color-text--orange-200{color:#ffcc80 !important}.mdl-color--orange-200{background-color:#ffcc80 !important}.mdl-color-text--orange-300{color:#ffb74d !important}.mdl-color--orange-300{background-color:#ffb74d !important}.mdl-color-text--orange-400{color:#ffa726 !important}.mdl-color--orange-400{background-color:#ffa726 !important}.mdl-color-text--orange-500{color:#ff9800 !important}.mdl-color--orange-500{background-color:#ff9800 !important}.mdl-color-text--orange-600{color:#fb8c00 !important}.mdl-color--orange-600{background-color:#fb8c00 !important}.mdl-color-text--orange-700{color:#f57c00 !important}.mdl-color--orange-700{background-color:#f57c00 !important}.mdl-color-text--orange-800{color:#ef6c00 !important}.mdl-color--orange-800{background-color:#ef6c00 !important}.mdl-color-text--orange-900{color:#e65100 !important}.mdl-color--orange-900{background-color:#e65100 !important}.mdl-color-text--orange-A100{color:#ffd180 !important}.mdl-color--orange-A100{background-color:#ffd180 !important}.mdl-color-text--orange-A200{color:#ffab40 !important}.mdl-color--orange-A200{background-color:#ffab40 !important}.mdl-color-text--orange-A400{color:#ff9100 !important}.mdl-color--orange-A400{background-color:#ff9100 !important}.mdl-color-text--orange-A700{color:#ff6d00 !important}.mdl-color--orange-A700{background-color:#ff6d00 !important}.mdl-color-text--deep-orange{color:#ff5722 !important}.mdl-color--deep-orange{background-color:#ff5722 !important}.mdl-color-text--deep-orange-50{color:#fbe9e7 !important}.mdl-color--deep-orange-50{background-color:#fbe9e7 !important}.mdl-color-text--deep-orange-100{color:#ffccbc !important}.mdl-color--deep-orange-100{background-color:#ffccbc !important}.mdl-color-text--deep-orange-200{color:#ffab91 !important}.mdl-color--deep-orange-200{background-color:#ffab91 !important}.mdl-color-text--deep-orange-300{color:#ff8a65 !important}.mdl-color--deep-orange-300{background-color:#ff8a65 !important}.mdl-color-text--deep-orange-400{color:#ff7043 !important}.mdl-color--deep-orange-400{background-color:#ff7043 !important}.mdl-color-text--deep-orange-500{color:#ff5722 !important}.mdl-color--deep-orange-500{background-color:#ff5722 !important}.mdl-color-text--deep-orange-600{color:#f4511e !important}.mdl-color--deep-orange-600{background-color:#f4511e !important}.mdl-color-text--deep-orange-700{color:#e64a19 !important}.mdl-color--deep-orange-700{background-color:#e64a19 !important}.mdl-color-text--deep-orange-800{color:#d84315 !important}.mdl-color--deep-orange-800{background-color:#d84315 !important}.mdl-color-text--deep-orange-900{color:#bf360c !important}.mdl-color--deep-orange-900{background-color:#bf360c !important}.mdl-color-text--deep-orange-A100{color:#ff9e80 !important}.mdl-color--deep-orange-A100{background-color:#ff9e80 !important}.mdl-color-text--deep-orange-A200{color:#ff6e40 !important}.mdl-color--deep-orange-A200{background-color:#ff6e40 !important}.mdl-color-text--deep-orange-A400{color:#ff3d00 !important}.mdl-color--deep-orange-A400{background-color:#ff3d00 !important}.mdl-color-text--deep-orange-A700{color:#dd2c00 !important}.mdl-color--deep-orange-A700{background-color:#dd2c00 !important}.mdl-color-text--brown{color:#795548 !important}.mdl-color--brown{background-color:#795548 !important}.mdl-color-text--brown-50{color:#efebe9 !important}.mdl-color--brown-50{background-color:#efebe9 !important}.mdl-color-text--brown-100{color:#d7ccc8 !important}.mdl-color--brown-100{background-color:#d7ccc8 !important}.mdl-color-text--brown-200{color:#bcaaa4 !important}.mdl-color--brown-200{background-color:#bcaaa4 !important}.mdl-color-text--brown-300{color:#a1887f !important}.mdl-color--brown-300{background-color:#a1887f !important}.mdl-color-text--brown-400{color:#8d6e63 !important}.mdl-color--brown-400{background-color:#8d6e63 !important}.mdl-color-text--brown-500{color:#795548 !important}.mdl-color--brown-500{background-color:#795548 !important}.mdl-color-text--brown-600{color:#6d4c41 !important}.mdl-color--brown-600{background-color:#6d4c41 !important}.mdl-color-text--brown-700{color:#5d4037 !important}.mdl-color--brown-700{background-color:#5d4037 !important}.mdl-color-text--brown-800{color:#4e342e !important}.mdl-color--brown-800{background-color:#4e342e !important}.mdl-color-text--brown-900{color:#3e2723 !important}.mdl-color--brown-900{background-color:#3e2723 !important}.mdl-color-text--grey{color:#9e9e9e !important}.mdl-color--grey{background-color:#9e9e9e !important}.mdl-color-text--grey-50{color:#fafafa !important}.mdl-color--grey-50{background-color:#fafafa !important}.mdl-color-text--grey-100{color:#f5f5f5 !important}.mdl-color--grey-100{background-color:#f5f5f5 !important}.mdl-color-text--grey-200{color:#eee !important}.mdl-color--grey-200{background-color:#eee !important}.mdl-color-text--grey-300{color:#e0e0e0 !important}.mdl-color--grey-300{background-color:#e0e0e0 !important}.mdl-color-text--grey-400{color:#bdbdbd !important}.mdl-color--grey-400{background-color:#bdbdbd !important}.mdl-color-text--grey-500{color:#9e9e9e !important}.mdl-color--grey-500{background-color:#9e9e9e !important}.mdl-color-text--grey-600{color:#757575 !important}.mdl-color--grey-600{background-color:#757575 !important}.mdl-color-text--grey-700{color:#616161 !important}.mdl-color--grey-700{background-color:#616161 !important}.mdl-color-text--grey-800{color:#424242 !important}.mdl-color--grey-800{background-color:#424242 !important}.mdl-color-text--grey-900{color:#212121 !important}.mdl-color--grey-900{background-color:#212121 !important}.mdl-color-text--blue-grey{color:#607d8b !important}.mdl-color--blue-grey{background-color:#607d8b !important}.mdl-color-text--blue-grey-50{color:#eceff1 !important}.mdl-color--blue-grey-50{background-color:#eceff1 !important}.mdl-color-text--blue-grey-100{color:#cfd8dc !important}.mdl-color--blue-grey-100{background-color:#cfd8dc !important}.mdl-color-text--blue-grey-200{color:#b0bec5 !important}.mdl-color--blue-grey-200{background-color:#b0bec5 !important}.mdl-color-text--blue-grey-300{color:#90a4ae !important}.mdl-color--blue-grey-300{background-color:#90a4ae !important}.mdl-color-text--blue-grey-400{color:#78909c !important}.mdl-color--blue-grey-400{background-color:#78909c !important}.mdl-color-text--blue-grey-500{color:#607d8b !important}.mdl-color--blue-grey-500{background-color:#607d8b !important}.mdl-color-text--blue-grey-600{color:#546e7a !important}.mdl-color--blue-grey-600{background-color:#546e7a !important}.mdl-color-text--blue-grey-700{color:#455a64 !important}.mdl-color--blue-grey-700{background-color:#455a64 !important}.mdl-color-text--blue-grey-800{color:#37474f !important}.mdl-color--blue-grey-800{background-color:#37474f !important}.mdl-color-text--blue-grey-900{color:#263238 !important}.mdl-color--blue-grey-900{background-color:#263238 !important}.mdl-color--black{background-color:#000 !important}.mdl-color-text--black{color:#000 !important}.mdl-color--white{background-color:#fff !important}.mdl-color-text--white{color:#fff !important}.mdl-color--primary{background-color:rgb(255,152,0)!important}.mdl-color--primary-contrast{background-color:rgb(66,66,66)!important}.mdl-color--primary-dark{background-color:rgb(245,124,0)!important}.mdl-color--accent{background-color:rgb(64,196,255)!important}.mdl-color--accent-contrast{background-color:rgb(66,66,66)!important}.mdl-color-text--primary{color:rgb(255,152,0)!important}.mdl-color-text--primary-contrast{color:rgb(66,66,66)!important}.mdl-color-text--primary-dark{color:rgb(245,124,0)!important}.mdl-color-text--accent{color:rgb(64,196,255)!important}.mdl-color-text--accent-contrast{color:rgb(66,66,66)!important}.mdl-ripple{background:#000;border-radius:50%;height:50px;left:0;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:50px;overflow:hidden}.mdl-ripple.is-animating{transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1);transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1),-webkit-transform .3s cubic-bezier(0,0,.2,1)}.mdl-ripple.is-visible{opacity:.3}.mdl-animation--default,.mdl-animation--fast-out-slow-in{transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-animation--linear-out-slow-in{transition-timing-function:cubic-bezier(0,0,.2,1)}.mdl-animation--fast-out-linear-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.mdl-badge{position:relative;white-space:nowrap;margin-right:24px}.mdl-badge:not([data-badge]){margin-right:auto}.mdl-badge[data-badge]:after{content:attr(data-badge);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:absolute;top:-11px;right:-24px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:600;font-size:12px;width:22px;height:22px;border-radius:50%;background:rgb(64,196,255);color:rgb(66,66,66)}.mdl-button .mdl-badge[data-badge]:after{top:-10px;right:-5px}.mdl-badge.mdl-badge--no-background[data-badge]:after{color:rgb(64,196,255);background:rgba(66,66,66,.2);box-shadow:0 0 1px gray}.mdl-badge.mdl-badge--overlap{margin-right:10px}.mdl-badge.mdl-badge--overlap:after{right:-10px}.mdl-button{background:0 0;border:none;border-radius:2px;color:#000;position:relative;height:36px;margin:0;min-width:64px;padding:0 16px;display:inline-block;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;letter-spacing:0;overflow:hidden;will-change:box-shadow;transition:box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);outline:none;cursor:pointer;text-decoration:none;text-align:center;line-height:36px;vertical-align:middle}.mdl-button::-moz-focus-inner{border:0}.mdl-button:hover{background-color:rgba(158,158,158,.2)}.mdl-button:focus:not(:active){background-color:rgba(0,0,0,.12)}.mdl-button:active{background-color:rgba(158,158,158,.4)}.mdl-button.mdl-button--colored{color:rgb(255,152,0)}.mdl-button.mdl-button--colored:focus:not(:active){background-color:rgba(0,0,0,.12)}input.mdl-button[type="submit"]{-webkit-appearance:none}.mdl-button--raised{background:rgba(158,158,158,.2);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-button--raised:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--raised:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--raised.mdl-button--colored{background:rgb(255,152,0);color:rgb(66,66,66)}.mdl-button--raised.mdl-button--colored:hover{background-color:rgb(255,152,0)}.mdl-button--raised.mdl-button--colored:active{background-color:rgb(255,152,0)}.mdl-button--raised.mdl-button--colored:focus:not(:active){background-color:rgb(255,152,0)}.mdl-button--raised.mdl-button--colored .mdl-ripple{background:rgb(66,66,66)}.mdl-button--fab{border-radius:50%;font-size:24px;height:56px;margin:auto;min-width:56px;width:56px;padding:0;overflow:hidden;background:rgba(158,158,158,.2);box-shadow:0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);position:relative;line-height:normal}.mdl-button--fab .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--fab.mdl-button--mini-fab{height:40px;min-width:40px;width:40px}.mdl-button--fab .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button--fab:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--fab:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--fab.mdl-button--colored{background:rgb(64,196,255);color:rgb(66,66,66)}.mdl-button--fab.mdl-button--colored:hover{background-color:rgb(64,196,255)}.mdl-button--fab.mdl-button--colored:focus:not(:active){background-color:rgb(64,196,255)}.mdl-button--fab.mdl-button--colored:active{background-color:rgb(64,196,255)}.mdl-button--fab.mdl-button--colored .mdl-ripple{background:rgb(66,66,66)}.mdl-button--icon{border-radius:50%;font-size:24px;height:32px;margin-left:0;margin-right:0;min-width:32px;width:32px;padding:0;overflow:hidden;color:inherit;line-height:normal}.mdl-button--icon .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon{height:24px;min-width:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon .material-icons{top:0;left:0}.mdl-button--icon .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button__ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-button[disabled] .mdl-button__ripple-container .mdl-ripple,.mdl-button.mdl-button--disabled .mdl-button__ripple-container .mdl-ripple{background-color:transparent}.mdl-button--primary.mdl-button--primary{color:rgb(255,152,0)}.mdl-button--primary.mdl-button--primary .mdl-ripple{background:rgb(66,66,66)}.mdl-button--primary.mdl-button--primary.mdl-button--raised,.mdl-button--primary.mdl-button--primary.mdl-button--fab{color:rgb(66,66,66);background-color:rgb(255,152,0)}.mdl-button--accent.mdl-button--accent{color:rgb(64,196,255)}.mdl-button--accent.mdl-button--accent .mdl-ripple{background:rgb(66,66,66)}.mdl-button--accent.mdl-button--accent.mdl-button--raised,.mdl-button--accent.mdl-button--accent.mdl-button--fab{color:rgb(66,66,66);background-color:rgb(64,196,255)}.mdl-button[disabled][disabled],.mdl-button.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26);cursor:default;background-color:transparent}.mdl-button--fab[disabled][disabled],.mdl-button--fab.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-button--raised[disabled][disabled],.mdl-button--raised.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26);box-shadow:none}.mdl-button--colored[disabled][disabled],.mdl-button--colored.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26)}.mdl-button .material-icons{vertical-align:middle}.mdl-card{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;font-size:16px;font-weight:400;min-height:200px;overflow:hidden;width:330px;z-index:1;position:relative;background:#fff;border-radius:2px;box-sizing:border-box}.mdl-card__media{background-color:rgb(64,196,255);background-repeat:repeat;background-position:50% 50%;background-size:cover;background-origin:padding-box;background-attachment:scroll;box-sizing:border-box}.mdl-card__title{-webkit-align-items:center;-ms-flex-align:center;align-items:center;color:#000;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;line-height:normal;padding:16px;-webkit-perspective-origin:165px 56px;perspective-origin:165px 56px;-webkit-transform-origin:165px 56px;transform-origin:165px 56px;box-sizing:border-box}.mdl-card__title.mdl-card--border{border-bottom:1px solid rgba(0,0,0,.1)}.mdl-card__title-text{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;color:inherit;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:24px;font-weight:300;line-height:normal;overflow:hidden;-webkit-transform-origin:149px 48px;transform-origin:149px 48px;margin:0}.mdl-card__subtitle-text{font-size:14px;color:rgba(0,0,0,.54);margin:0}.mdl-card__supporting-text{color:rgba(0,0,0,.54);font-size:1rem;line-height:18px;overflow:hidden;padding:16px;width:90%}.mdl-card__actions{font-size:16px;line-height:normal;width:100%;background-color:transparent;padding:8px;box-sizing:border-box}.mdl-card__actions.mdl-card--border{border-top:1px solid rgba(0,0,0,.1)}.mdl-card--expand{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-card__menu{position:absolute;right:16px;top:16px}.mdl-checkbox{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0}.mdl-checkbox.is-upgraded{padding-left:24px}.mdl-checkbox__input{line-height:24px}.mdl-checkbox.is-upgraded .mdl-checkbox__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-checkbox__box-outline{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;overflow:hidden;border:2px solid rgba(0,0,0,.54);border-radius:2px;z-index:2}.mdl-checkbox.is-checked .mdl-checkbox__box-outline{border:2px solid rgb(255,152,0)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__box-outline,.mdl-checkbox.is-disabled .mdl-checkbox__box-outline{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__focus-helper{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:transparent}.mdl-checkbox.is-focused .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-checkbox.is-focused.is-checked .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(255,152,0,.26);background-color:rgba(255,152,0,.26)}.mdl-checkbox__tick-outline{position:absolute;top:0;left:0;height:100%;width:100%;-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");background:0 0;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background}.mdl-checkbox.is-checked .mdl-checkbox__tick-outline{background:rgb(255,152,0)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}fieldset[disabled] .mdl-checkbox.is-checked .mdl-checkbox__tick-outline,.mdl-checkbox.is-checked.is-disabled .mdl-checkbox__tick-outline{background:rgba(0,0,0,.26)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}.mdl-checkbox__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0}fieldset[disabled] .mdl-checkbox .mdl-checkbox__label,.mdl-checkbox.is-disabled .mdl-checkbox__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__ripple-container{position:absolute;z-index:2;top:-6px;left:-10px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-checkbox__ripple-container .mdl-ripple{background:rgb(255,152,0)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container{cursor:auto}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container .mdl-ripple,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container .mdl-ripple{background:0 0}.mdl-chip{height:32px;font-family:"Roboto","Helvetica","Arial",sans-serif;line-height:32px;padding:0 12px;border:0;border-radius:16px;background-color:#dedede;display:inline-block;color:rgba(0,0,0,.87);margin:2px 0;font-size:0;white-space:nowrap}.mdl-chip__text{font-size:13px;vertical-align:middle;display:inline-block}.mdl-chip__action{height:24px;width:24px;background:0 0;opacity:.54;cursor:pointer;padding:0;margin:0 0 0 4px;font-size:13px;text-decoration:none;color:rgba(0,0,0,.87);border:none;outline:none}.mdl-chip__action,.mdl-chip__contact{display:inline-block;vertical-align:middle;overflow:hidden;text-align:center}.mdl-chip__contact{height:32px;width:32px;border-radius:16px;margin-right:8px;font-size:18px;line-height:32px}.mdl-chip:focus{outline:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-chip:active{background-color:#d6d6d6}.mdl-chip--deletable{padding-right:4px}.mdl-chip--contact{padding-left:0}.mdl-data-table{position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff}.mdl-data-table thead{padding-bottom:3px}.mdl-data-table thead .mdl-data-table__select{margin-top:0}.mdl-data-table tbody tr{position:relative;height:48px;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background-color}.mdl-data-table tbody tr.is-selected{background-color:#e0e0e0}.mdl-data-table tbody tr:hover{background-color:#eee}.mdl-data-table td{text-align:right}.mdl-data-table th{padding:0 18px 12px 18px;text-align:right}.mdl-data-table td:first-of-type,.mdl-data-table th:first-of-type{padding-left:24px}.mdl-data-table td:last-of-type,.mdl-data-table th:last-of-type{padding-right:24px}.mdl-data-table td{position:relative;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px;box-sizing:border-box}.mdl-data-table td,.mdl-data-table td .mdl-data-table__select{vertical-align:middle}.mdl-data-table th{position:relative;vertical-align:bottom;text-overflow:ellipsis;font-weight:700;line-height:24px;letter-spacing:0;height:48px;font-size:12px;color:rgba(0,0,0,.54);padding-bottom:8px;box-sizing:border-box}.mdl-data-table th.mdl-data-table__header--sorted-ascending,.mdl-data-table th.mdl-data-table__header--sorted-descending{color:rgba(0,0,0,.87)}.mdl-data-table th.mdl-data-table__header--sorted-ascending:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:before{font-family:'Material Icons';font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased;font-size:16px;content:"\e5d8";margin-right:5px;vertical-align:sub}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover{cursor:pointer}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover:before{color:rgba(0,0,0,.26)}.mdl-data-table th.mdl-data-table__header--sorted-descending:before{content:"\e5db"}.mdl-data-table__select{width:16px}.mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric{text-align:left}.mdl-dialog{border:none;box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2);width:280px}.mdl-dialog__title{padding:24px 24px 0;margin:0;font-size:2.5rem}.mdl-dialog__actions{padding:8px 8px 8px 24px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.mdl-dialog__actions>*{margin-right:8px;height:36px}.mdl-dialog__actions>*:first-child{margin-right:0}.mdl-dialog__actions--full-width{padding:0 0 8px}.mdl-dialog__actions--full-width>*{height:48px;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;padding-right:16px;margin-right:0;text-align:right}.mdl-dialog__content{padding:20px 24px 24px;color:rgba(0,0,0,.54)}.mdl-mega-footer{padding:16px 40px;color:#9e9e9e;background-color:#424242}.mdl-mega-footer--top-section:after,.mdl-mega-footer--middle-section:after,.mdl-mega-footer--bottom-section:after,.mdl-mega-footer__top-section:after,.mdl-mega-footer__middle-section:after,.mdl-mega-footer__bottom-section:after{content:'';display:block;clear:both}.mdl-mega-footer--left-section,.mdl-mega-footer__left-section,.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{margin-bottom:16px}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:block;margin-bottom:16px;color:inherit;text-decoration:none}@media screen and (min-width:760px){.mdl-mega-footer--left-section,.mdl-mega-footer__left-section{float:left}.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{float:right}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:inline-block;margin-left:16px;line-height:36px;vertical-align:middle}}.mdl-mega-footer--social-btn,.mdl-mega-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{display:block;position:relative}@media screen and (min-width:760px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{width:33%}.mdl-mega-footer--drop-down-section:nth-child(1),.mdl-mega-footer--drop-down-section:nth-child(2),.mdl-mega-footer__drop-down-section:nth-child(1),.mdl-mega-footer__drop-down-section:nth-child(2){float:left}.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(3){float:right}.mdl-mega-footer--drop-down-section:nth-child(3):after,.mdl-mega-footer__drop-down-section:nth-child(3):after{clear:right}.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section:nth-child(4){clear:right;float:right}.mdl-mega-footer--middle-section:after,.mdl-mega-footer__middle-section:after{content:'';display:block;clear:both}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:0}}@media screen and (min-width:1024px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section,.mdl-mega-footer__drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(4){width:24%;float:left}}.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{position:absolute;width:100%;height:55.8px;padding:32px;margin:-16px 0 0;cursor:pointer;z-index:1;opacity:0}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CE'}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list{display:none}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CF'}.mdl-mega-footer--heading,.mdl-mega-footer__heading{position:relative;width:100%;padding-right:39.8px;margin-bottom:16px;box-sizing:border-box;font-size:14px;line-height:23.8px;font-weight:500;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#e0e0e0}.mdl-mega-footer--heading:after,.mdl-mega-footer__heading:after{content:'';position:absolute;top:0;right:0;display:block;width:23.8px;height:23.8px;background-size:cover}.mdl-mega-footer--link-list,.mdl-mega-footer__link-list{list-style:none;padding:0;margin:0 0 32px}.mdl-mega-footer--link-list:after,.mdl-mega-footer__link-list:after{clear:both;display:block;content:''}.mdl-mega-footer--link-list li,.mdl-mega-footer__link-list li{font-size:14px;font-weight:400;letter-spacing:0;line-height:20px}.mdl-mega-footer--link-list a,.mdl-mega-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}@media screen and (min-width:760px){.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{display:none}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{content:''}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list{display:block}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{content:''}}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:16px;margin-bottom:16px}.mdl-logo{margin-bottom:16px;color:#fff}.mdl-mega-footer--bottom-section .mdl-mega-footer--link-list li,.mdl-mega-footer__bottom-section .mdl-mega-footer__link-list li{float:left;margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-logo{float:left;margin-bottom:0;margin-right:16px}}.mdl-mini-footer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:32px 16px;color:#9e9e9e;background-color:#424242}.mdl-mini-footer:after{content:'';display:block}.mdl-mini-footer .mdl-logo{line-height:36px}.mdl-mini-footer--link-list,.mdl-mini-footer__link-list{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;list-style:none;margin:0;padding:0}.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{line-height:36px}}.mdl-mini-footer--link-list a,.mdl-mini-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}.mdl-mini-footer--left-section,.mdl-mini-footer__left-section{display:inline-block;-webkit-order:0;-ms-flex-order:0;order:0}.mdl-mini-footer--right-section,.mdl-mini-footer__right-section{display:inline-block;-webkit-order:1;-ms-flex-order:1;order:1}.mdl-mini-footer--social-btn,.mdl-mini-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-icon-toggle{position:relative;z-index:1;vertical-align:middle;display:inline-block;height:32px;margin:0;padding:0}.mdl-icon-toggle__input{line-height:32px}.mdl-icon-toggle.is-upgraded .mdl-icon-toggle__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-icon-toggle__label{display:inline-block;position:relative;cursor:pointer;height:32px;width:32px;min-width:32px;color:#616161;border-radius:50%;padding:0;margin-left:0;margin-right:0;text-align:center;background-color:transparent;will-change:background-color;transition:background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1)}.mdl-icon-toggle__label.material-icons{line-height:32px;font-size:24px}.mdl-icon-toggle.is-checked .mdl-icon-toggle__label{color:rgb(255,152,0)}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__label{color:rgba(0,0,0,.26);cursor:auto;transition:none}.mdl-icon-toggle.is-focused .mdl-icon-toggle__label{background-color:rgba(0,0,0,.12)}.mdl-icon-toggle.is-focused.is-checked .mdl-icon-toggle__label{background-color:rgba(255,152,0,.26)}.mdl-icon-toggle__ripple-container{position:absolute;z-index:2;top:-2px;left:-2px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-icon-toggle__ripple-container .mdl-ripple{background:#616161}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container{cursor:auto}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container .mdl-ripple{background:0 0}.mdl-list{display:block;padding:8px 0;list-style:none}.mdl-list__item{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:.04em;line-height:1;min-height:48px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;padding:16px;cursor:default;color:rgba(0,0,0,.87);overflow:hidden}.mdl-list__item,.mdl-list__item .mdl-list__item-primary-content{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.mdl-list__item .mdl-list__item-primary-content{-webkit-order:0;-ms-flex-order:0;order:0;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2;text-decoration:none}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-icon{margin-right:32px}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-avatar{margin-right:16px}.mdl-list__item .mdl-list__item-secondary-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column;-ms-flex-flow:column;flex-flow:column;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;margin-left:16px}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-action label{display:inline}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-info{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;color:rgba(0,0,0,.54)}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-sub-header{padding:0 0 0 16px}.mdl-list__item-icon,.mdl-list__item-icon.material-icons{height:24px;width:24px;font-size:24px;box-sizing:border-box;color:#757575}.mdl-list__item-avatar,.mdl-list__item-avatar.material-icons{height:40px;width:40px;box-sizing:border-box;border-radius:50%;background-color:#757575;font-size:40px;color:#fff}.mdl-list__item--two-line{height:72px}.mdl-list__item--two-line .mdl-list__item-primary-content{height:36px;line-height:20px;display:block}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-avatar{float:left}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left;margin-top:6px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-secondary-content{height:36px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-sub-title{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-list__item--three-line{height:88px}.mdl-list__item--three-line .mdl-list__item-primary-content{height:52px;line-height:20px;display:block}.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-avatar,.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left}.mdl-list__item--three-line .mdl-list__item-secondary-content{height:52px}.mdl-list__item--three-line .mdl-list__item-text-body{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;height:52px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-menu__container{display:block;margin:0;padding:0;border:none;position:absolute;overflow:visible;height:0;width:0;visibility:hidden;z-index:-1}.mdl-menu__container.is-visible,.mdl-menu__container.is-animating{z-index:999;visibility:visible}.mdl-menu__outline{display:block;background:#fff;margin:0;padding:0;border:none;border-radius:2px;position:absolute;top:0;left:0;overflow:hidden;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;transform-origin:0 0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);will-change:transform;transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1);z-index:-1}.mdl-menu__container.is-visible .mdl-menu__outline{opacity:1;-webkit-transform:scale(1);transform:scale(1);z-index:999}.mdl-menu__outline.mdl-menu--bottom-right{-webkit-transform-origin:100% 0;transform-origin:100% 0}.mdl-menu__outline.mdl-menu--top-left{-webkit-transform-origin:0 100%;transform-origin:0 100%}.mdl-menu__outline.mdl-menu--top-right{-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.mdl-menu{position:absolute;list-style:none;top:0;left:0;height:auto;width:auto;min-width:124px;padding:8px 0;margin:0;opacity:0;clip:rect(0 0 0 0);z-index:-1}.mdl-menu__container.is-visible .mdl-menu{opacity:1;z-index:999}.mdl-menu.is-animating{transition:opacity .2s cubic-bezier(.4,0,.2,1),clip .3s cubic-bezier(.4,0,.2,1)}.mdl-menu.mdl-menu--bottom-right{left:auto;right:0}.mdl-menu.mdl-menu--top-left{top:auto;bottom:0}.mdl-menu.mdl-menu--top-right{top:auto;left:auto;bottom:0;right:0}.mdl-menu.mdl-menu--unaligned{top:auto;left:auto}.mdl-menu__item{display:block;border:none;color:rgba(0,0,0,.87);background-color:transparent;text-align:left;margin:0;padding:0 16px;outline-color:#bdbdbd;position:relative;overflow:hidden;font-size:14px;font-weight:400;letter-spacing:0;text-decoration:none;cursor:pointer;height:48px;line-height:48px;white-space:nowrap;opacity:0;transition:opacity .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-menu__container.is-visible .mdl-menu__item{opacity:1}.mdl-menu__item::-moz-focus-inner{border:0}.mdl-menu__item--full-bleed-divider{border-bottom:1px solid rgba(0,0,0,.12)}.mdl-menu__item[disabled],.mdl-menu__item[data-mdl-disabled]{color:#bdbdbd;background-color:transparent;cursor:auto}.mdl-menu__item[disabled]:hover,.mdl-menu__item[data-mdl-disabled]:hover{background-color:transparent}.mdl-menu__item[disabled]:focus,.mdl-menu__item[data-mdl-disabled]:focus{background-color:transparent}.mdl-menu__item[disabled] .mdl-ripple,.mdl-menu__item[data-mdl-disabled] .mdl-ripple{background:0 0}.mdl-menu__item:hover{background-color:#eee}.mdl-menu__item:focus{outline:none;background-color:#eee}.mdl-menu__item:active{background-color:#e0e0e0}.mdl-menu__item--ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-progress{display:block;position:relative;height:4px;width:500px;max-width:100%}.mdl-progress>.bar{display:block;position:absolute;top:0;bottom:0;width:0%;transition:width .2s cubic-bezier(.4,0,.2,1)}.mdl-progress>.progressbar{background-color:rgb(255,152,0);z-index:1;left:0}.mdl-progress>.bufferbar{background-image:linear-gradient(to right,rgba(66,66,66,.7),rgba(66,66,66,.7)),linear-gradient(to right,rgb(255,152,0),rgb(255,152,0));z-index:0;left:0}.mdl-progress>.auxbar{right:0}@supports (-webkit-appearance:none){.mdl-progress:not(.mdl-progress--indeterminate):not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate):not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(66,66,66,.7),rgba(66,66,66,.7)),linear-gradient(to right,rgb(255,152,0),rgb(255,152,0));-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=")}}.mdl-progress:not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(66,66,66,.9),rgba(66,66,66,.9)),linear-gradient(to right,rgb(255,152,0),rgb(255,152,0))}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1{-webkit-animation-name:indeterminate1;animation-name:indeterminate1}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1,.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-color:rgb(255,152,0);-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-image:none;-webkit-animation-name:indeterminate2;animation-name:indeterminate2}@-webkit-keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@-webkit-keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}@keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}.mdl-navigation{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;box-sizing:border-box}.mdl-navigation__link{color:#424242;text-decoration:none;margin:0;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0;opacity:.87}.mdl-navigation__link .material-icons{vertical-align:middle}.mdl-layout{width:100%;height:100%;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow-y:auto;overflow-x:hidden;position:relative;-webkit-overflow-scrolling:touch}.mdl-layout.is-small-screen .mdl-layout--large-screen-only{display:none}.mdl-layout:not(.is-small-screen) .mdl-layout--small-screen-only{display:none}.mdl-layout__container{position:absolute;width:100%;height:100%}.mdl-layout__title,.mdl-layout-title{display:block;position:relative;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400;box-sizing:border-box}.mdl-layout-spacer{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-layout__drawer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:240px;height:100%;max-height:100%;position:absolute;top:0;left:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);box-sizing:border-box;border-right:1px solid #e0e0e0;background:#fafafa;-webkit-transform:translateX(-250px);transform:translateX(-250px);-webkit-transform-style:preserve-3d;transform-style:preserve-3d;will-change:transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;color:#424242;overflow:visible;overflow-y:auto;z-index:5}.mdl-layout__drawer.is-visible{-webkit-transform:translateX(0);transform:translateX(0)}.mdl-layout__drawer.is-visible~.mdl-layout__content.mdl-layout__content{overflow:hidden}.mdl-layout__drawer>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:64px;padding-left:40px}@media screen and (max-width:1024px){.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:56px;padding-left:16px}}.mdl-layout__drawer .mdl-navigation{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-ms-flex-align:stretch;-ms-grid-row-align:stretch;align-items:stretch;padding-top:16px}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{display:block;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;padding:16px 40px;margin:0;color:#757575}@media screen and (max-width:1024px){.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{padding:16px}}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link:hover{background-color:#e0e0e0}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link--current{background-color:#e0e0e0;color:#000}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__drawer{-webkit-transform:translateX(0);transform:translateX(0)}}.mdl-layout__drawer-button{display:block;position:absolute;height:48px;width:48px;border:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden;text-align:center;cursor:pointer;font-size:26px;line-height:56px;font-family:Helvetica,Arial,sans-serif;margin:8px 12px;top:0;left:0;color:rgb(66,66,66);z-index:4}.mdl-layout__header .mdl-layout__drawer-button{position:absolute;color:rgb(66,66,66);background-color:inherit}@media screen and (max-width:1024px){.mdl-layout__header .mdl-layout__drawer-button{margin:4px}}@media screen and (max-width:1024px){.mdl-layout__drawer-button{margin:4px;color:rgba(0,0,0,.5)}}@media screen and (min-width:1025px){.mdl-layout__drawer-button{line-height:54px}.mdl-layout--no-desktop-drawer-button .mdl-layout__drawer-button,.mdl-layout--fixed-drawer>.mdl-layout__drawer-button,.mdl-layout--no-drawer-button .mdl-layout__drawer-button{display:none}}.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;box-sizing:border-box;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;width:100%;margin:0;padding:0;border:none;min-height:64px;max-height:1000px;z-index:3;background-color:rgb(255,152,0);color:rgb(66,66,66);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:max-height,box-shadow}@media screen and (max-width:1024px){.mdl-layout__header{min-height:56px}}.mdl-layout--fixed-drawer.is-upgraded:not(.is-small-screen)>.mdl-layout__header{margin-left:240px;width:calc(100% - 240px)}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__header .mdl-layout__header-row{padding-left:40px}}.mdl-layout__header>.mdl-layout-icon{position:absolute;left:40px;top:16px;height:32px;width:32px;overflow:hidden;z-index:3;display:block}@media screen and (max-width:1024px){.mdl-layout__header>.mdl-layout-icon{left:16px;top:12px}}.mdl-layout.has-drawer .mdl-layout__header>.mdl-layout-icon{display:none}.mdl-layout__header.is-compact{max-height:64px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact{max-height:56px}}.mdl-layout__header.is-compact.has-tabs{height:112px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact.has-tabs{min-height:104px}}@media screen and (max-width:1024px){.mdl-layout__header{display:none}.mdl-layout--fixed-header>.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex}}.mdl-layout__header--transparent.mdl-layout__header--transparent{background-color:transparent;box-shadow:none}.mdl-layout__header--seamed,.mdl-layout__header--scroll{box-shadow:none}.mdl-layout__header--waterfall{box-shadow:none;overflow:hidden}.mdl-layout__header--waterfall.is-casting-shadow{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-layout__header--waterfall.mdl-layout__header--waterfall-hide-top{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.mdl-layout__header-row{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:64px;margin:0;padding:0 40px 0 80px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:40px}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__header-row{padding-left:40px}}@media screen and (max-width:1024px){.mdl-layout__header-row{height:56px;padding:0 16px 0 72px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:16px}}.mdl-layout__header-row>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__header--scroll .mdl-layout__header-row{width:100%}.mdl-layout__header-row .mdl-navigation{margin:0;padding:0;height:64px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation{height:56px}}.mdl-layout__header-row .mdl-navigation__link{display:block;color:rgb(66,66,66);line-height:64px;padding:0 24px}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation__link{line-height:56px;padding:0 16px}}.mdl-layout__obfuscator{background-color:transparent;position:absolute;top:0;left:0;height:100%;width:100%;z-index:4;visibility:hidden;transition-property:background-color;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-layout__obfuscator.is-visible{background-color:rgba(0,0,0,.5);visibility:visible}@supports (pointer-events:auto){.mdl-layout__obfuscator{background-color:rgba(0,0,0,.5);opacity:0;transition-property:opacity;visibility:visible;pointer-events:none}.mdl-layout__obfuscator.is-visible{pointer-events:auto;opacity:1}}.mdl-layout__content{-ms-flex:0 1 auto;position:relative;display:inline-block;overflow-y:auto;overflow-x:hidden;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;z-index:1;-webkit-overflow-scrolling:touch}.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:240px}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow:visible}@media screen and (max-width:1024px){.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:0}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow-y:auto;overflow-x:hidden}}.mdl-layout__tab-bar{height:96px;margin:0;width:calc(100% - 112px);padding:0 0 0 56px;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:rgb(255,152,0);overflow-y:hidden;overflow-x:scroll}.mdl-layout__tab-bar::-webkit-scrollbar{display:none}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}}@media screen and (max-width:1024px){.mdl-layout__tab-bar{width:calc(100% - 60px);padding:0 0 0 60px}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{width:calc(100% - 8px);padding-left:4px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar{padding:0;overflow:hidden;width:100%}.mdl-layout__tab-bar-container{position:relative;height:48px;width:100%;border:none;margin:0;z-index:2;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden}.mdl-layout__container>.mdl-layout__tab-bar-container{position:absolute;top:0;left:0}.mdl-layout__tab-bar-button{display:inline-block;position:absolute;top:0;height:48px;width:56px;z-index:4;text-align:center;background-color:rgb(255,152,0);color:transparent;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button{width:16px}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button .material-icons,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button .material-icons{position:relative;left:-4px}@media screen and (max-width:1024px){.mdl-layout__tab-bar-button{width:60px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar-button{display:none}.mdl-layout__tab-bar-button .material-icons{line-height:48px}.mdl-layout__tab-bar-button.is-active{color:rgb(66,66,66)}.mdl-layout__tab-bar-left-button{left:0}.mdl-layout__tab-bar-right-button{right:0}.mdl-layout__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(66,66,66,.6);overflow:hidden}@media screen and (max-width:1024px){.mdl-layout__tab{padding:0 12px}}.mdl-layout--fixed-tabs .mdl-layout__tab{float:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;padding:0}.mdl-layout.is-upgraded .mdl-layout__tab.is-active{color:rgb(66,66,66)}.mdl-layout.is-upgraded .mdl-layout__tab.is-active::after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(64,196,255);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-layout__tab .mdl-layout__tab-ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-layout__tab .mdl-layout__tab-ripple-container .mdl-ripple{background-color:rgb(66,66,66)}.mdl-layout__tab-panel{display:block}.mdl-layout.is-upgraded .mdl-layout__tab-panel{display:none}.mdl-layout.is-upgraded .mdl-layout__tab-panel.is-active{display:block}.mdl-radio{position:relative;font-size:16px;line-height:24px;display:inline-block;box-sizing:border-box;margin:0;padding-left:0}.mdl-radio.is-upgraded{padding-left:24px}.mdl-radio__button{line-height:24px}.mdl-radio.is-upgraded .mdl-radio__button{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-radio__outer-circle{position:absolute;top:4px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;border:2px solid rgba(0,0,0,.54);border-radius:50%;z-index:2}.mdl-radio.is-checked .mdl-radio__outer-circle{border:2px solid rgb(255,152,0)}.mdl-radio__outer-circle fieldset[disabled] .mdl-radio,.mdl-radio.is-disabled .mdl-radio__outer-circle{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-radio__inner-circle{position:absolute;z-index:1;margin:0;top:8px;left:4px;box-sizing:border-box;width:8px;height:8px;cursor:pointer;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;-webkit-transform:scale3d(0,0,0);transform:scale3d(0,0,0);border-radius:50%;background:rgb(255,152,0)}.mdl-radio.is-checked .mdl-radio__inner-circle{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}fieldset[disabled] .mdl-radio .mdl-radio__inner-circle,.mdl-radio.is-disabled .mdl-radio__inner-circle{background:rgba(0,0,0,.26);cursor:auto}.mdl-radio.is-focused .mdl-radio__inner-circle{box-shadow:0 0 0 10px rgba(0,0,0,.1)}.mdl-radio__label{cursor:pointer}fieldset[disabled] .mdl-radio .mdl-radio__label,.mdl-radio.is-disabled .mdl-radio__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-radio__ripple-container{position:absolute;z-index:2;top:-9px;left:-13px;box-sizing:border-box;width:42px;height:42px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-radio__ripple-container .mdl-ripple{background:rgb(255,152,0)}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container,.mdl-radio.is-disabled .mdl-radio__ripple-container{cursor:auto}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container .mdl-ripple,.mdl-radio.is-disabled .mdl-radio__ripple-container .mdl-ripple{background:0 0}_:-ms-input-placeholder,:root .mdl-slider.mdl-slider.is-upgraded{-ms-appearance:none;height:32px;margin:0}.mdl-slider{width:calc(100% - 40px);margin:0 20px}.mdl-slider.is-upgraded{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:2px;background:0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:0;padding:0;color:rgb(255,152,0);-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;z-index:1;cursor:pointer}.mdl-slider.is-upgraded::-moz-focus-outer{border:0}.mdl-slider.is-upgraded::-ms-tooltip{display:none}.mdl-slider.is-upgraded::-webkit-slider-runnable-track{background:0 0}.mdl-slider.is-upgraded::-moz-range-track{background:0 0;border:none}.mdl-slider.is-upgraded::-ms-track{background:0 0;color:transparent;height:2px;width:100%;border:none}.mdl-slider.is-upgraded::-ms-fill-lower{padding:0;background:linear-gradient(to right,transparent,transparent 16px,rgb(255,152,0)16px,rgb(255,152,0)0)}.mdl-slider.is-upgraded::-ms-fill-upper{padding:0;background:linear-gradient(to left,transparent,transparent 16px,rgba(0,0,0,.26)16px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background:rgb(255,152,0);border:none;transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded::-moz-range-thumb{-moz-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background-image:none;background:rgb(255,152,0);border:none}.mdl-slider.is-upgraded:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(255,152,0,.26)}.mdl-slider.is-upgraded:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(255,152,0,.26)}.mdl-slider.is-upgraded:active::-webkit-slider-thumb{background-image:none;background:rgb(255,152,0);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded:active::-moz-range-thumb{background-image:none;background:rgb(255,152,0);transform:scale(1.5)}.mdl-slider.is-upgraded::-ms-thumb{width:32px;height:32px;border:none;border-radius:50%;background:rgb(255,152,0);transform:scale(.375);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgb(255,152,0)0%,rgb(255,152,0)37.5%,rgba(255,152,0,.26)37.5%,rgba(255,152,0,.26)100%);transform:scale(1)}.mdl-slider.is-upgraded:active::-ms-thumb{background:rgb(255,152,0);transform:scale(.5625)}.mdl-slider.is-upgraded.is-lowest-value::-webkit-slider-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-moz-range-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:active::-webkit-slider-thumb{border:1.6px solid rgba(0,0,0,.26);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:9px}.mdl-slider.is-upgraded.is-lowest-value:active::-moz-range-thumb{border:1.5px solid rgba(0,0,0,.26);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value::-ms-thumb{background:radial-gradient(circle closest-side,transparent 0%,transparent 66.67%,rgba(0,0,0,.26)66.67%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgba(0,0,0,.12)0%,rgba(0,0,0,.12)25%,rgba(0,0,0,.26)25%,rgba(0,0,0,.26)37.5%,rgba(0,0,0,.12)37.5%,rgba(0,0,0,.12)100%);transform:scale(1)}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-thumb{transform:scale(.5625);background:radial-gradient(circle closest-side,transparent 0%,transparent 77.78%,rgba(0,0,0,.26)77.78%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-lower{background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-fill-upper{margin-left:9px}.mdl-slider.is-upgraded:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled::-webkit-slider-thumb{-webkit-transform:scale(.667);transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded:disabled::-moz-range-thumb{transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-lower{background-color:rgba(0,0,0,.26);left:-6px}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-webkit-slider-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;-webkit-transform:scale(.667);transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-moz-range-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded:disabled:active::-ms-thumb,.mdl-slider.is-upgraded:disabled::-ms-thumb{transform:scale(.25);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-ms-thumb{transform:scale(.25);background:radial-gradient(circle closest-side,transparent 0%,transparent 50%,rgba(0,0,0,.26)50%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded:disabled::-ms-fill-lower{margin-right:6px;background:linear-gradient(to right,transparent,transparent 25px,rgba(0,0,0,.26)25px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded:disabled::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-fill-upper{margin-left:6px}.mdl-slider__ie-container{height:18px;overflow:visible;border:none;margin:none;padding:none}.mdl-slider__container{height:18px;position:relative;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.mdl-slider__container,.mdl-slider__background-flex{background:0 0;display:-webkit-flex;display:-ms-flexbox;display:flex}.mdl-slider__background-flex{position:absolute;height:2px;width:calc(100% - 52px);top:50%;left:0;margin:0 26px;overflow:hidden;border:0;padding:0;-webkit-transform:translate(0,-1px);transform:translate(0,-1px)}.mdl-slider__background-lower{background:rgb(255,152,0)}.mdl-slider__background-lower,.mdl-slider__background-upper{-webkit-flex:0;-ms-flex:0;flex:0;position:relative;border:0;padding:0}.mdl-slider__background-upper{background:rgba(0,0,0,.26);transition:left .18s cubic-bezier(.4,0,.2,1)}.mdl-snackbar{position:fixed;bottom:0;left:50%;cursor:default;background-color:#323232;z-index:3;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:"Roboto","Helvetica","Arial",sans-serif;will-change:transform;-webkit-transform:translate(0,80px);transform:translate(0,80px);transition:transform .25s cubic-bezier(.4,0,1,1);transition:transform .25s cubic-bezier(.4,0,1,1),-webkit-transform .25s cubic-bezier(.4,0,1,1);pointer-events:none}@media (max-width:479px){.mdl-snackbar{width:100%;left:0;min-height:48px;max-height:80px}}@media (min-width:480px){.mdl-snackbar{min-width:288px;max-width:568px;border-radius:2px;-webkit-transform:translate(-50%,80px);transform:translate(-50%,80px)}}.mdl-snackbar--active{-webkit-transform:translate(0,0);transform:translate(0,0);pointer-events:auto;transition:transform .25s cubic-bezier(0,0,.2,1);transition:transform .25s cubic-bezier(0,0,.2,1),-webkit-transform .25s cubic-bezier(0,0,.2,1)}@media (min-width:480px){.mdl-snackbar--active{-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}}.mdl-snackbar__text{padding:14px 12px 14px 24px;vertical-align:middle;color:#fff;float:left}.mdl-snackbar__action{background:0 0;border:none;color:rgb(64,196,255);float:right;padding:14px 24px 14px 12px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;line-height:1;letter-spacing:0;overflow:hidden;outline:none;opacity:0;pointer-events:none;cursor:pointer;text-decoration:none;text-align:center;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-snackbar__action::-moz-focus-inner{border:0}.mdl-snackbar__action:not([aria-hidden]){opacity:1;pointer-events:auto}.mdl-spinner{display:inline-block;position:relative;width:28px;height:28px}.mdl-spinner:not(.is-upgraded).is-active:after{content:"Loading..."}.mdl-spinner.is-upgraded.is-active{-webkit-animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite;animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite}@-webkit-keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.mdl-spinner__layer{position:absolute;width:100%;height:100%;opacity:0}.mdl-spinner__layer-1{border-color:#42a5f5}.mdl-spinner--single-color .mdl-spinner__layer-1{border-color:rgb(255,152,0)}.mdl-spinner.is-active .mdl-spinner__layer-1{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-2{border-color:#f44336}.mdl-spinner--single-color .mdl-spinner__layer-2{border-color:rgb(255,152,0)}.mdl-spinner.is-active .mdl-spinner__layer-2{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-3{border-color:#fdd835}.mdl-spinner--single-color .mdl-spinner__layer-3{border-color:rgb(255,152,0)}.mdl-spinner.is-active .mdl-spinner__layer-3{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-4{border-color:#4caf50}.mdl-spinner--single-color .mdl-spinner__layer-4{border-color:rgb(255,152,0)}.mdl-spinner.is-active .mdl-spinner__layer-4{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@-webkit-keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}@keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}.mdl-spinner__gap-patch{position:absolute;box-sizing:border-box;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__gap-patch .mdl-spinner__circle{width:1000%;left:-450%}.mdl-spinner__circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__circle-clipper .mdl-spinner__circle{width:200%}.mdl-spinner__circle{box-sizing:border-box;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent!important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0;left:0}.mdl-spinner__left .mdl-spinner__circle{border-right-color:transparent!important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.mdl-spinner.is-active .mdl-spinner__left .mdl-spinner__circle{-webkit-animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__right .mdl-spinner__circle{left:-100%;border-left-color:transparent!important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.mdl-spinner.is-active .mdl-spinner__right .mdl-spinner__circle{-webkit-animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}@keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}.mdl-switch{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0;overflow:visible;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-switch.is-upgraded{padding-left:28px}.mdl-switch__input{line-height:24px}.mdl-switch.is-upgraded .mdl-switch__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-switch__track{background:rgba(0,0,0,.26);position:absolute;left:0;top:5px;height:14px;width:36px;border-radius:14px;cursor:pointer}.mdl-switch.is-checked .mdl-switch__track{background:rgba(255,152,0,.5)}.mdl-switch__track fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__track{background:rgba(0,0,0,.12);cursor:auto}.mdl-switch__thumb{background:#fafafa;position:absolute;left:0;top:2px;height:20px;width:20px;border-radius:50%;cursor:pointer;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:left}.mdl-switch.is-checked .mdl-switch__thumb{background:rgb(255,152,0);left:16px;box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-switch__thumb fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__thumb{background:#bdbdbd;cursor:auto}.mdl-switch__focus-helper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-4px,-4px);transform:translate(-4px,-4px);display:inline-block;box-sizing:border-box;width:8px;height:8px;border-radius:50%;background-color:transparent}.mdl-switch.is-focused .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-switch.is-focused.is-checked .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(255,152,0,.26);background-color:rgba(255,152,0,.26)}.mdl-switch__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0;left:24px}.mdl-switch__label fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__label{color:#bdbdbd;cursor:auto}.mdl-switch__ripple-container{position:absolute;z-index:2;top:-12px;left:-14px;box-sizing:border-box;width:48px;height:48px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000);transition-duration:.4s;transition-timing-function:step-end;transition-property:left}.mdl-switch__ripple-container .mdl-ripple{background:rgb(255,152,0)}.mdl-switch__ripple-container fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__ripple-container{cursor:auto}fieldset[disabled] .mdl-switch .mdl-switch__ripple-container .mdl-ripple,.mdl-switch.is-disabled .mdl-switch__ripple-container .mdl-ripple{background:0 0}.mdl-switch.is-checked .mdl-switch__ripple-container{left:2px}.mdl-tabs{display:block;width:100%}.mdl-tabs__tab-bar{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:space-between;-ms-flex-line-pack:justify;align-content:space-between;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;height:48px;padding:0;margin:0;border-bottom:1px solid #e0e0e0}.mdl-tabs__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(0,0,0,.54);overflow:hidden}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active{color:rgba(0,0,0,.87)}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active:after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(255,152,0);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-tabs__tab .mdl-tabs__ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-tabs__tab .mdl-tabs__ripple-container .mdl-ripple{background:rgb(255,152,0)}.mdl-tabs__panel{display:block}.mdl-tabs.is-upgraded .mdl-tabs__panel{display:none}.mdl-tabs.is-upgraded .mdl-tabs__panel.is-active{display:block}@-webkit-keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}@keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}.mdl-textfield{position:relative;font-size:16px;display:inline-block;box-sizing:border-box;width:300px;max-width:100%;margin:0;padding:20px 0}.mdl-textfield .mdl-button{position:absolute;bottom:20px}.mdl-textfield--align-right{text-align:right}.mdl-textfield--full-width{width:100%}.mdl-textfield--expandable{min-width:32px;width:auto;min-height:32px}.mdl-textfield--expandable .mdl-button--icon{top:16px}.mdl-textfield__input{border:none;border-bottom:1px solid rgba(0,0,0,.12);display:block;font-size:16px;font-family:"Helvetica","Arial",sans-serif;margin:0;padding:4px 0;width:100%;background:0 0;text-align:left;color:inherit}.mdl-textfield__input[type="number"]{-moz-appearance:textfield}.mdl-textfield__input[type="number"]::-webkit-inner-spin-button,.mdl-textfield__input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.mdl-textfield.is-focused .mdl-textfield__input{outline:none}.mdl-textfield.is-invalid .mdl-textfield__input{border-color:#d50000;box-shadow:none}fieldset[disabled] .mdl-textfield .mdl-textfield__input,.mdl-textfield.is-disabled .mdl-textfield__input{background-color:transparent;border-bottom:1px dotted rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-textfield textarea.mdl-textfield__input{display:block}.mdl-textfield__label{bottom:0;color:rgba(0,0,0,.26);font-size:16px;left:0;right:0;pointer-events:none;position:absolute;display:block;top:24px;width:100%;overflow:hidden;white-space:nowrap;text-align:left}.mdl-textfield.is-dirty .mdl-textfield__label,.mdl-textfield.has-placeholder .mdl-textfield__label{visibility:hidden}.mdl-textfield--floating-label .mdl-textfield__label{transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{transition:none}fieldset[disabled] .mdl-textfield .mdl-textfield__label,.mdl-textfield.is-disabled.is-disabled .mdl-textfield__label{color:rgba(0,0,0,.26)}.mdl-textfield--floating-label.is-focused .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{color:rgb(255,152,0);font-size:12px;top:4px;visibility:visible}.mdl-textfield--floating-label.is-focused .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__expandable-holder .mdl-textfield__label{top:-16px}.mdl-textfield--floating-label.is-invalid .mdl-textfield__label{color:#d50000;font-size:12px}.mdl-textfield__label:after{background-color:rgb(255,152,0);bottom:20px;content:'';height:2px;left:45%;position:absolute;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);visibility:hidden;width:10px}.mdl-textfield.is-focused .mdl-textfield__label:after{left:0;visibility:visible;width:100%}.mdl-textfield.is-invalid .mdl-textfield__label:after{background-color:#d50000}.mdl-textfield__error{color:#d50000;position:absolute;font-size:12px;margin-top:3px;visibility:hidden;display:block}.mdl-textfield.is-invalid .mdl-textfield__error{visibility:visible}.mdl-textfield__expandable-holder{display:inline-block;position:relative;margin-left:32px;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-block;max-width:.1px}.mdl-textfield.is-focused .mdl-textfield__expandable-holder,.mdl-textfield.is-dirty .mdl-textfield__expandable-holder{max-width:600px}.mdl-textfield__expandable-holder .mdl-textfield__label:after{bottom:0}.mdl-tooltip{-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:top center;transform-origin:top center;z-index:999;background:rgba(97,97,97,.9);border-radius:2px;color:#fff;display:inline-block;font-size:10px;font-weight:500;line-height:14px;max-width:170px;position:fixed;top:-500px;left:-500px;padding:8px;text-align:center}.mdl-tooltip.is-active{-webkit-animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards;animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards}.mdl-tooltip--large{line-height:14px;font-size:14px;padding:16px}@-webkit-keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}@keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}.mdl-shadow--2dp{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-shadow--3dp{box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-shadow--4dp{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2)}.mdl-shadow--6dp{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.2)}.mdl-shadow--8dp{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.2)}.mdl-shadow--16dp{box-shadow:0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)}.mdl-shadow--24dp{box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2)}.mdl-grid{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;margin:0 auto;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.mdl-grid.mdl-grid--no-spacing{padding:0}.mdl-cell{box-sizing:border-box}.mdl-cell--top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.mdl-cell--middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-cell--bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.mdl-cell--stretch{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.mdl-grid.mdl-grid--no-spacing>.mdl-cell{margin:0}.mdl-cell--order-1{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12{-webkit-order:12;-ms-flex-order:12;order:12}@media (max-width:479px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:100%}.mdl-cell--hide-phone{display:none!important}.mdl-cell--order-1-phone.mdl-cell--order-1-phone{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-phone.mdl-cell--order-2-phone{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-phone.mdl-cell--order-3-phone{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-phone.mdl-cell--order-4-phone{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-phone.mdl-cell--order-5-phone{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-phone.mdl-cell--order-6-phone{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-phone.mdl-cell--order-7-phone{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-phone.mdl-cell--order-8-phone{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-phone.mdl-cell--order-9-phone{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-phone.mdl-cell--order-10-phone{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-phone.mdl-cell--order-11-phone{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-phone.mdl-cell--order-12-phone{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:25%}.mdl-cell--2-col,.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:50%}.mdl-cell--3-col,.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:75%}.mdl-cell--4-col,.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:100%}.mdl-cell--5-col,.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:100%}.mdl-cell--6-col,.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:100%}.mdl-cell--7-col,.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:100%}.mdl-cell--8-col,.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:25%}.mdl-cell--2-offset,.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:50%}.mdl-cell--3-offset,.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:75%}}@media (min-width:480px) and (max-width:839px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:50%}.mdl-cell--hide-tablet{display:none!important}.mdl-cell--order-1-tablet.mdl-cell--order-1-tablet{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-tablet.mdl-cell--order-2-tablet{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-tablet.mdl-cell--order-3-tablet{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-tablet.mdl-cell--order-4-tablet{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-tablet.mdl-cell--order-5-tablet{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-tablet.mdl-cell--order-6-tablet{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-tablet.mdl-cell--order-7-tablet{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-tablet.mdl-cell--order-8-tablet{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-tablet.mdl-cell--order-9-tablet{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-tablet.mdl-cell--order-10-tablet{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-tablet.mdl-cell--order-11-tablet{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-tablet.mdl-cell--order-12-tablet{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:calc(12.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:12.5%}.mdl-cell--2-col,.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:25%}.mdl-cell--3-col,.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:calc(37.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:37.5%}.mdl-cell--4-col,.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:50%}.mdl-cell--5-col,.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:calc(62.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:62.5%}.mdl-cell--6-col,.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:75%}.mdl-cell--7-col,.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:calc(87.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:87.5%}.mdl-cell--8-col,.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:calc(12.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:12.5%}.mdl-cell--2-offset,.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:25%}.mdl-cell--3-offset,.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:calc(37.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:37.5%}.mdl-cell--4-offset,.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:50%}.mdl-cell--5-offset,.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:calc(62.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:62.5%}.mdl-cell--6-offset,.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:75%}.mdl-cell--7-offset,.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:calc(87.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:87.5%}}@media (min-width:840px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:33.3333333333%}.mdl-cell--hide-desktop{display:none!important}.mdl-cell--order-1-desktop.mdl-cell--order-1-desktop{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-desktop.mdl-cell--order-2-desktop{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-desktop.mdl-cell--order-3-desktop{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-desktop.mdl-cell--order-4-desktop{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-desktop.mdl-cell--order-5-desktop{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-desktop.mdl-cell--order-6-desktop{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-desktop.mdl-cell--order-7-desktop{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-desktop.mdl-cell--order-8-desktop{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-desktop.mdl-cell--order-9-desktop{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-desktop.mdl-cell--order-10-desktop{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-desktop.mdl-cell--order-11-desktop{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-desktop.mdl-cell--order-12-desktop{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:calc(8.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:8.3333333333%}.mdl-cell--2-col,.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:calc(16.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:16.6666666667%}.mdl-cell--3-col,.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:25%}.mdl-cell--4-col,.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:33.3333333333%}.mdl-cell--5-col,.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:calc(41.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:41.6666666667%}.mdl-cell--6-col,.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:50%}.mdl-cell--7-col,.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:calc(58.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:58.3333333333%}.mdl-cell--8-col,.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:calc(66.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:66.6666666667%}.mdl-cell--9-col,.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:75%}.mdl-cell--10-col,.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:calc(83.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:83.3333333333%}.mdl-cell--11-col,.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:calc(91.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:91.6666666667%}.mdl-cell--12-col,.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:calc(8.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:8.3333333333%}.mdl-cell--2-offset,.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:calc(16.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:16.6666666667%}.mdl-cell--3-offset,.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:25%}.mdl-cell--4-offset,.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:calc(33.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:33.3333333333%}.mdl-cell--5-offset,.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:calc(41.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:41.6666666667%}.mdl-cell--6-offset,.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:50%}.mdl-cell--7-offset,.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:calc(58.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:58.3333333333%}.mdl-cell--8-offset,.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:calc(66.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:66.6666666667%}.mdl-cell--9-offset,.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:75%}.mdl-cell--10-offset,.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:calc(83.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:83.3333333333%}.mdl-cell--11-offset,.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:calc(91.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:91.6666666667%}}body{margin:0}.styleguide-demo h1{margin:48px 24px 0}.styleguide-demo h1:after{content:'';display:block;width:100%;border-bottom:1px solid rgba(0,0,0,.5);margin-top:24px}.styleguide-demo{opacity:0;transition:opacity .6s ease}.styleguide-masthead{height:256px;background:#212121;padding:115px 16px 0}.styleguide-container{position:relative;max-width:960px;width:100%}.styleguide-title{color:#fff;bottom:auto;position:relative;font-size:56px;font-weight:300;line-height:1;letter-spacing:-.02em}.styleguide-title:after{border-bottom:0}.styleguide-title span{font-weight:300}.mdl-styleguide .mdl-layout__drawer .mdl-navigation__link{padding:10px 24px}.demosLoaded .styleguide-demo{opacity:1}iframe{display:block;width:100%;border:none}iframe.heightSet{overflow:hidden}.demo-wrapper{margin:24px}.demo-wrapper iframe{border:1px solid rgba(0,0,0,.5)} \ No newline at end of file diff --git a/modules/material/www/material.red-teal.1.2.1.min.css b/modules/material/www/material.red-teal.1.2.1.min.css new file mode 100644 index 00000000..02ae702a --- /dev/null +++ b/modules/material/www/material.red-teal.1.2.1.min.css @@ -0,0 +1,8 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.2.1 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +@charset "UTF-8";html{color:rgba(0,0,0,.87)}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.browserupgrade{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.hidden{display:none!important}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}@media print{*,*:before,*:after,*:first-letter{background:transparent!important;color:#000!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}a,.mdl-accordion,.mdl-button,.mdl-card,.mdl-checkbox,.mdl-dropdown-menu,.mdl-icon-toggle,.mdl-item,.mdl-radio,.mdl-slider,.mdl-switch,.mdl-tabs__tab{-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:rgba(255,255,255,0)}html{width:100%;height:100%;-ms-touch-action:manipulation;touch-action:manipulation}body{width:100%;min-height:100%}main{display:block}*[hidden]{display:none!important}html,body{font-family:"Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:20px}h1,h2,h3,h4,h5,h6,p{padding:0}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400;line-height:1.35;letter-spacing:-.02em;opacity:.54;font-size:.6em}h1{font-size:56px;line-height:1.35;letter-spacing:-.02em;margin:24px 0}h1,h2{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h2{font-size:45px;line-height:48px}h2,h3{margin:24px 0}h3{font-size:34px;line-height:40px}h3,h4{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h4{font-size:24px;line-height:32px;-moz-osx-font-smoothing:grayscale;margin:24px 0 16px}h5{font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;margin:24px 0 16px}h6{font-size:16px;letter-spacing:.04em}h6,p{font-weight:400;line-height:24px}p{font-size:14px;letter-spacing:0;margin:0 0 16px}a{color:rgb(100,255,218);font-weight:500}blockquote{font-family:"Roboto","Helvetica","Arial",sans-serif;position:relative;font-size:24px;font-weight:300;font-style:italic;line-height:1.35;letter-spacing:.08em}blockquote:before{position:absolute;left:-.5em;content:'“'}blockquote:after{content:'”';margin-left:-.05em}mark{background-color:#f4ff81}dt{font-weight:700}address{font-size:12px;line-height:1;font-style:normal}address,ul,ol{font-weight:400;letter-spacing:0}ul,ol{font-size:14px;line-height:24px}.mdl-typography--display-4,.mdl-typography--display-4-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:112px;font-weight:300;line-height:1;letter-spacing:-.04em}.mdl-typography--display-4-color-contrast{opacity:.54}.mdl-typography--display-3,.mdl-typography--display-3-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:56px;font-weight:400;line-height:1.35;letter-spacing:-.02em}.mdl-typography--display-3-color-contrast{opacity:.54}.mdl-typography--display-2,.mdl-typography--display-2-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:45px;font-weight:400;line-height:48px}.mdl-typography--display-2-color-contrast{opacity:.54}.mdl-typography--display-1,.mdl-typography--display-1-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:34px;font-weight:400;line-height:40px}.mdl-typography--display-1-color-contrast{opacity:.54}.mdl-typography--headline,.mdl-typography--headline-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:24px;font-weight:400;line-height:32px;-moz-osx-font-smoothing:grayscale}.mdl-typography--headline-color-contrast{opacity:.87}.mdl-typography--title,.mdl-typography--title-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}.mdl-typography--title-color-contrast{opacity:.87}.mdl-typography--subhead,.mdl-typography--subhead-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;line-height:24px;letter-spacing:.04em}.mdl-typography--subhead-color-contrast{opacity:.87}.mdl-typography--body-2,.mdl-typography--body-2-color-contrast{font-size:14px;font-weight:700;line-height:24px;letter-spacing:0}.mdl-typography--body-2-color-contrast{opacity:.87}.mdl-typography--body-1,.mdl-typography--body-1-color-contrast{font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-color-contrast{opacity:.87}.mdl-typography--body-2-force-preferred-font,.mdl-typography--body-2-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:24px;letter-spacing:0}.mdl-typography--body-2-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--body-1-force-preferred-font,.mdl-typography--body-1-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--caption,.mdl-typography--caption-force-preferred-font{font-size:12px;font-weight:400;line-height:1;letter-spacing:0}.mdl-typography--caption-force-preferred-font{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--caption-color-contrast,.mdl-typography--caption-force-preferred-font-color-contrast{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;opacity:.54}.mdl-typography--caption-force-preferred-font-color-contrast,.mdl-typography--menu{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--menu{font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--menu-color-contrast{opacity:.87}.mdl-typography--menu-color-contrast,.mdl-typography--button,.mdl-typography--button-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--button,.mdl-typography--button-color-contrast{text-transform:uppercase}.mdl-typography--button-color-contrast{opacity:.87}.mdl-typography--text-left{text-align:left}.mdl-typography--text-right{text-align:right}.mdl-typography--text-center{text-align:center}.mdl-typography--text-justify{text-align:justify}.mdl-typography--text-nowrap{white-space:nowrap}.mdl-typography--text-lowercase{text-transform:lowercase}.mdl-typography--text-uppercase{text-transform:uppercase}.mdl-typography--text-capitalize{text-transform:capitalize}.mdl-typography--font-thin{font-weight:200!important}.mdl-typography--font-light{font-weight:300!important}.mdl-typography--font-regular{font-weight:400!important}.mdl-typography--font-medium{font-weight:500!important}.mdl-typography--font-bold{font-weight:700!important}.mdl-typography--font-black{font-weight:900!important}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased}.mdl-color-text--red{color:#f44336 !important}.mdl-color--red{background-color:#f44336 !important}.mdl-color-text--red-50{color:#ffebee !important}.mdl-color--red-50{background-color:#ffebee !important}.mdl-color-text--red-100{color:#ffcdd2 !important}.mdl-color--red-100{background-color:#ffcdd2 !important}.mdl-color-text--red-200{color:#ef9a9a !important}.mdl-color--red-200{background-color:#ef9a9a !important}.mdl-color-text--red-300{color:#e57373 !important}.mdl-color--red-300{background-color:#e57373 !important}.mdl-color-text--red-400{color:#ef5350 !important}.mdl-color--red-400{background-color:#ef5350 !important}.mdl-color-text--red-500{color:#f44336 !important}.mdl-color--red-500{background-color:#f44336 !important}.mdl-color-text--red-600{color:#e53935 !important}.mdl-color--red-600{background-color:#e53935 !important}.mdl-color-text--red-700{color:#d32f2f !important}.mdl-color--red-700{background-color:#d32f2f !important}.mdl-color-text--red-800{color:#c62828 !important}.mdl-color--red-800{background-color:#c62828 !important}.mdl-color-text--red-900{color:#b71c1c !important}.mdl-color--red-900{background-color:#b71c1c !important}.mdl-color-text--red-A100{color:#ff8a80 !important}.mdl-color--red-A100{background-color:#ff8a80 !important}.mdl-color-text--red-A200{color:#ff5252 !important}.mdl-color--red-A200{background-color:#ff5252 !important}.mdl-color-text--red-A400{color:#ff1744 !important}.mdl-color--red-A400{background-color:#ff1744 !important}.mdl-color-text--red-A700{color:#d50000 !important}.mdl-color--red-A700{background-color:#d50000 !important}.mdl-color-text--pink{color:#e91e63 !important}.mdl-color--pink{background-color:#e91e63 !important}.mdl-color-text--pink-50{color:#fce4ec !important}.mdl-color--pink-50{background-color:#fce4ec !important}.mdl-color-text--pink-100{color:#f8bbd0 !important}.mdl-color--pink-100{background-color:#f8bbd0 !important}.mdl-color-text--pink-200{color:#f48fb1 !important}.mdl-color--pink-200{background-color:#f48fb1 !important}.mdl-color-text--pink-300{color:#f06292 !important}.mdl-color--pink-300{background-color:#f06292 !important}.mdl-color-text--pink-400{color:#ec407a !important}.mdl-color--pink-400{background-color:#ec407a !important}.mdl-color-text--pink-500{color:#e91e63 !important}.mdl-color--pink-500{background-color:#e91e63 !important}.mdl-color-text--pink-600{color:#d81b60 !important}.mdl-color--pink-600{background-color:#d81b60 !important}.mdl-color-text--pink-700{color:#c2185b !important}.mdl-color--pink-700{background-color:#c2185b !important}.mdl-color-text--pink-800{color:#ad1457 !important}.mdl-color--pink-800{background-color:#ad1457 !important}.mdl-color-text--pink-900{color:#880e4f !important}.mdl-color--pink-900{background-color:#880e4f !important}.mdl-color-text--pink-A100{color:#ff80ab !important}.mdl-color--pink-A100{background-color:#ff80ab !important}.mdl-color-text--pink-A200{color:#ff4081 !important}.mdl-color--pink-A200{background-color:#ff4081 !important}.mdl-color-text--pink-A400{color:#f50057 !important}.mdl-color--pink-A400{background-color:#f50057 !important}.mdl-color-text--pink-A700{color:#c51162 !important}.mdl-color--pink-A700{background-color:#c51162 !important}.mdl-color-text--purple{color:#9c27b0 !important}.mdl-color--purple{background-color:#9c27b0 !important}.mdl-color-text--purple-50{color:#f3e5f5 !important}.mdl-color--purple-50{background-color:#f3e5f5 !important}.mdl-color-text--purple-100{color:#e1bee7 !important}.mdl-color--purple-100{background-color:#e1bee7 !important}.mdl-color-text--purple-200{color:#ce93d8 !important}.mdl-color--purple-200{background-color:#ce93d8 !important}.mdl-color-text--purple-300{color:#ba68c8 !important}.mdl-color--purple-300{background-color:#ba68c8 !important}.mdl-color-text--purple-400{color:#ab47bc !important}.mdl-color--purple-400{background-color:#ab47bc !important}.mdl-color-text--purple-500{color:#9c27b0 !important}.mdl-color--purple-500{background-color:#9c27b0 !important}.mdl-color-text--purple-600{color:#8e24aa !important}.mdl-color--purple-600{background-color:#8e24aa !important}.mdl-color-text--purple-700{color:#7b1fa2 !important}.mdl-color--purple-700{background-color:#7b1fa2 !important}.mdl-color-text--purple-800{color:#6a1b9a !important}.mdl-color--purple-800{background-color:#6a1b9a !important}.mdl-color-text--purple-900{color:#4a148c !important}.mdl-color--purple-900{background-color:#4a148c !important}.mdl-color-text--purple-A100{color:#ea80fc !important}.mdl-color--purple-A100{background-color:#ea80fc !important}.mdl-color-text--purple-A200{color:#e040fb !important}.mdl-color--purple-A200{background-color:#e040fb !important}.mdl-color-text--purple-A400{color:#d500f9 !important}.mdl-color--purple-A400{background-color:#d500f9 !important}.mdl-color-text--purple-A700{color:#a0f !important}.mdl-color--purple-A700{background-color:#a0f !important}.mdl-color-text--deep-purple{color:#673ab7 !important}.mdl-color--deep-purple{background-color:#673ab7 !important}.mdl-color-text--deep-purple-50{color:#ede7f6 !important}.mdl-color--deep-purple-50{background-color:#ede7f6 !important}.mdl-color-text--deep-purple-100{color:#d1c4e9 !important}.mdl-color--deep-purple-100{background-color:#d1c4e9 !important}.mdl-color-text--deep-purple-200{color:#b39ddb !important}.mdl-color--deep-purple-200{background-color:#b39ddb !important}.mdl-color-text--deep-purple-300{color:#9575cd !important}.mdl-color--deep-purple-300{background-color:#9575cd !important}.mdl-color-text--deep-purple-400{color:#7e57c2 !important}.mdl-color--deep-purple-400{background-color:#7e57c2 !important}.mdl-color-text--deep-purple-500{color:#673ab7 !important}.mdl-color--deep-purple-500{background-color:#673ab7 !important}.mdl-color-text--deep-purple-600{color:#5e35b1 !important}.mdl-color--deep-purple-600{background-color:#5e35b1 !important}.mdl-color-text--deep-purple-700{color:#512da8 !important}.mdl-color--deep-purple-700{background-color:#512da8 !important}.mdl-color-text--deep-purple-800{color:#4527a0 !important}.mdl-color--deep-purple-800{background-color:#4527a0 !important}.mdl-color-text--deep-purple-900{color:#311b92 !important}.mdl-color--deep-purple-900{background-color:#311b92 !important}.mdl-color-text--deep-purple-A100{color:#b388ff !important}.mdl-color--deep-purple-A100{background-color:#b388ff !important}.mdl-color-text--deep-purple-A200{color:#7c4dff !important}.mdl-color--deep-purple-A200{background-color:#7c4dff !important}.mdl-color-text--deep-purple-A400{color:#651fff !important}.mdl-color--deep-purple-A400{background-color:#651fff !important}.mdl-color-text--deep-purple-A700{color:#6200ea !important}.mdl-color--deep-purple-A700{background-color:#6200ea !important}.mdl-color-text--indigo{color:#3f51b5 !important}.mdl-color--indigo{background-color:#3f51b5 !important}.mdl-color-text--indigo-50{color:#e8eaf6 !important}.mdl-color--indigo-50{background-color:#e8eaf6 !important}.mdl-color-text--indigo-100{color:#c5cae9 !important}.mdl-color--indigo-100{background-color:#c5cae9 !important}.mdl-color-text--indigo-200{color:#9fa8da !important}.mdl-color--indigo-200{background-color:#9fa8da !important}.mdl-color-text--indigo-300{color:#7986cb !important}.mdl-color--indigo-300{background-color:#7986cb !important}.mdl-color-text--indigo-400{color:#5c6bc0 !important}.mdl-color--indigo-400{background-color:#5c6bc0 !important}.mdl-color-text--indigo-500{color:#3f51b5 !important}.mdl-color--indigo-500{background-color:#3f51b5 !important}.mdl-color-text--indigo-600{color:#3949ab !important}.mdl-color--indigo-600{background-color:#3949ab !important}.mdl-color-text--indigo-700{color:#303f9f !important}.mdl-color--indigo-700{background-color:#303f9f !important}.mdl-color-text--indigo-800{color:#283593 !important}.mdl-color--indigo-800{background-color:#283593 !important}.mdl-color-text--indigo-900{color:#1a237e !important}.mdl-color--indigo-900{background-color:#1a237e !important}.mdl-color-text--indigo-A100{color:#8c9eff !important}.mdl-color--indigo-A100{background-color:#8c9eff !important}.mdl-color-text--indigo-A200{color:#536dfe !important}.mdl-color--indigo-A200{background-color:#536dfe !important}.mdl-color-text--indigo-A400{color:#3d5afe !important}.mdl-color--indigo-A400{background-color:#3d5afe !important}.mdl-color-text--indigo-A700{color:#304ffe !important}.mdl-color--indigo-A700{background-color:#304ffe !important}.mdl-color-text--blue{color:#2196f3 !important}.mdl-color--blue{background-color:#2196f3 !important}.mdl-color-text--blue-50{color:#e3f2fd !important}.mdl-color--blue-50{background-color:#e3f2fd !important}.mdl-color-text--blue-100{color:#bbdefb !important}.mdl-color--blue-100{background-color:#bbdefb !important}.mdl-color-text--blue-200{color:#90caf9 !important}.mdl-color--blue-200{background-color:#90caf9 !important}.mdl-color-text--blue-300{color:#64b5f6 !important}.mdl-color--blue-300{background-color:#64b5f6 !important}.mdl-color-text--blue-400{color:#42a5f5 !important}.mdl-color--blue-400{background-color:#42a5f5 !important}.mdl-color-text--blue-500{color:#2196f3 !important}.mdl-color--blue-500{background-color:#2196f3 !important}.mdl-color-text--blue-600{color:#1e88e5 !important}.mdl-color--blue-600{background-color:#1e88e5 !important}.mdl-color-text--blue-700{color:#1976d2 !important}.mdl-color--blue-700{background-color:#1976d2 !important}.mdl-color-text--blue-800{color:#1565c0 !important}.mdl-color--blue-800{background-color:#1565c0 !important}.mdl-color-text--blue-900{color:#0d47a1 !important}.mdl-color--blue-900{background-color:#0d47a1 !important}.mdl-color-text--blue-A100{color:#82b1ff !important}.mdl-color--blue-A100{background-color:#82b1ff !important}.mdl-color-text--blue-A200{color:#448aff !important}.mdl-color--blue-A200{background-color:#448aff !important}.mdl-color-text--blue-A400{color:#2979ff !important}.mdl-color--blue-A400{background-color:#2979ff !important}.mdl-color-text--blue-A700{color:#2962ff !important}.mdl-color--blue-A700{background-color:#2962ff !important}.mdl-color-text--light-blue{color:#03a9f4 !important}.mdl-color--light-blue{background-color:#03a9f4 !important}.mdl-color-text--light-blue-50{color:#e1f5fe !important}.mdl-color--light-blue-50{background-color:#e1f5fe !important}.mdl-color-text--light-blue-100{color:#b3e5fc !important}.mdl-color--light-blue-100{background-color:#b3e5fc !important}.mdl-color-text--light-blue-200{color:#81d4fa !important}.mdl-color--light-blue-200{background-color:#81d4fa !important}.mdl-color-text--light-blue-300{color:#4fc3f7 !important}.mdl-color--light-blue-300{background-color:#4fc3f7 !important}.mdl-color-text--light-blue-400{color:#29b6f6 !important}.mdl-color--light-blue-400{background-color:#29b6f6 !important}.mdl-color-text--light-blue-500{color:#03a9f4 !important}.mdl-color--light-blue-500{background-color:#03a9f4 !important}.mdl-color-text--light-blue-600{color:#039be5 !important}.mdl-color--light-blue-600{background-color:#039be5 !important}.mdl-color-text--light-blue-700{color:#0288d1 !important}.mdl-color--light-blue-700{background-color:#0288d1 !important}.mdl-color-text--light-blue-800{color:#0277bd !important}.mdl-color--light-blue-800{background-color:#0277bd !important}.mdl-color-text--light-blue-900{color:#01579b !important}.mdl-color--light-blue-900{background-color:#01579b !important}.mdl-color-text--light-blue-A100{color:#80d8ff !important}.mdl-color--light-blue-A100{background-color:#80d8ff !important}.mdl-color-text--light-blue-A200{color:#40c4ff !important}.mdl-color--light-blue-A200{background-color:#40c4ff !important}.mdl-color-text--light-blue-A400{color:#00b0ff !important}.mdl-color--light-blue-A400{background-color:#00b0ff !important}.mdl-color-text--light-blue-A700{color:#0091ea !important}.mdl-color--light-blue-A700{background-color:#0091ea !important}.mdl-color-text--cyan{color:#00bcd4 !important}.mdl-color--cyan{background-color:#00bcd4 !important}.mdl-color-text--cyan-50{color:#e0f7fa !important}.mdl-color--cyan-50{background-color:#e0f7fa !important}.mdl-color-text--cyan-100{color:#b2ebf2 !important}.mdl-color--cyan-100{background-color:#b2ebf2 !important}.mdl-color-text--cyan-200{color:#80deea !important}.mdl-color--cyan-200{background-color:#80deea !important}.mdl-color-text--cyan-300{color:#4dd0e1 !important}.mdl-color--cyan-300{background-color:#4dd0e1 !important}.mdl-color-text--cyan-400{color:#26c6da !important}.mdl-color--cyan-400{background-color:#26c6da !important}.mdl-color-text--cyan-500{color:#00bcd4 !important}.mdl-color--cyan-500{background-color:#00bcd4 !important}.mdl-color-text--cyan-600{color:#00acc1 !important}.mdl-color--cyan-600{background-color:#00acc1 !important}.mdl-color-text--cyan-700{color:#0097a7 !important}.mdl-color--cyan-700{background-color:#0097a7 !important}.mdl-color-text--cyan-800{color:#00838f !important}.mdl-color--cyan-800{background-color:#00838f !important}.mdl-color-text--cyan-900{color:#006064 !important}.mdl-color--cyan-900{background-color:#006064 !important}.mdl-color-text--cyan-A100{color:#84ffff !important}.mdl-color--cyan-A100{background-color:#84ffff !important}.mdl-color-text--cyan-A200{color:#18ffff !important}.mdl-color--cyan-A200{background-color:#18ffff !important}.mdl-color-text--cyan-A400{color:#00e5ff !important}.mdl-color--cyan-A400{background-color:#00e5ff !important}.mdl-color-text--cyan-A700{color:#00b8d4 !important}.mdl-color--cyan-A700{background-color:#00b8d4 !important}.mdl-color-text--teal{color:#009688 !important}.mdl-color--teal{background-color:#009688 !important}.mdl-color-text--teal-50{color:#e0f2f1 !important}.mdl-color--teal-50{background-color:#e0f2f1 !important}.mdl-color-text--teal-100{color:#b2dfdb !important}.mdl-color--teal-100{background-color:#b2dfdb !important}.mdl-color-text--teal-200{color:#80cbc4 !important}.mdl-color--teal-200{background-color:#80cbc4 !important}.mdl-color-text--teal-300{color:#4db6ac !important}.mdl-color--teal-300{background-color:#4db6ac !important}.mdl-color-text--teal-400{color:#26a69a !important}.mdl-color--teal-400{background-color:#26a69a !important}.mdl-color-text--teal-500{color:#009688 !important}.mdl-color--teal-500{background-color:#009688 !important}.mdl-color-text--teal-600{color:#00897b !important}.mdl-color--teal-600{background-color:#00897b !important}.mdl-color-text--teal-700{color:#00796b !important}.mdl-color--teal-700{background-color:#00796b !important}.mdl-color-text--teal-800{color:#00695c !important}.mdl-color--teal-800{background-color:#00695c !important}.mdl-color-text--teal-900{color:#004d40 !important}.mdl-color--teal-900{background-color:#004d40 !important}.mdl-color-text--teal-A100{color:#a7ffeb !important}.mdl-color--teal-A100{background-color:#a7ffeb !important}.mdl-color-text--teal-A200{color:#64ffda !important}.mdl-color--teal-A200{background-color:#64ffda !important}.mdl-color-text--teal-A400{color:#1de9b6 !important}.mdl-color--teal-A400{background-color:#1de9b6 !important}.mdl-color-text--teal-A700{color:#00bfa5 !important}.mdl-color--teal-A700{background-color:#00bfa5 !important}.mdl-color-text--green{color:#4caf50 !important}.mdl-color--green{background-color:#4caf50 !important}.mdl-color-text--green-50{color:#e8f5e9 !important}.mdl-color--green-50{background-color:#e8f5e9 !important}.mdl-color-text--green-100{color:#c8e6c9 !important}.mdl-color--green-100{background-color:#c8e6c9 !important}.mdl-color-text--green-200{color:#a5d6a7 !important}.mdl-color--green-200{background-color:#a5d6a7 !important}.mdl-color-text--green-300{color:#81c784 !important}.mdl-color--green-300{background-color:#81c784 !important}.mdl-color-text--green-400{color:#66bb6a !important}.mdl-color--green-400{background-color:#66bb6a !important}.mdl-color-text--green-500{color:#4caf50 !important}.mdl-color--green-500{background-color:#4caf50 !important}.mdl-color-text--green-600{color:#43a047 !important}.mdl-color--green-600{background-color:#43a047 !important}.mdl-color-text--green-700{color:#388e3c !important}.mdl-color--green-700{background-color:#388e3c !important}.mdl-color-text--green-800{color:#2e7d32 !important}.mdl-color--green-800{background-color:#2e7d32 !important}.mdl-color-text--green-900{color:#1b5e20 !important}.mdl-color--green-900{background-color:#1b5e20 !important}.mdl-color-text--green-A100{color:#b9f6ca !important}.mdl-color--green-A100{background-color:#b9f6ca !important}.mdl-color-text--green-A200{color:#69f0ae !important}.mdl-color--green-A200{background-color:#69f0ae !important}.mdl-color-text--green-A400{color:#00e676 !important}.mdl-color--green-A400{background-color:#00e676 !important}.mdl-color-text--green-A700{color:#00c853 !important}.mdl-color--green-A700{background-color:#00c853 !important}.mdl-color-text--light-green{color:#8bc34a !important}.mdl-color--light-green{background-color:#8bc34a !important}.mdl-color-text--light-green-50{color:#f1f8e9 !important}.mdl-color--light-green-50{background-color:#f1f8e9 !important}.mdl-color-text--light-green-100{color:#dcedc8 !important}.mdl-color--light-green-100{background-color:#dcedc8 !important}.mdl-color-text--light-green-200{color:#c5e1a5 !important}.mdl-color--light-green-200{background-color:#c5e1a5 !important}.mdl-color-text--light-green-300{color:#aed581 !important}.mdl-color--light-green-300{background-color:#aed581 !important}.mdl-color-text--light-green-400{color:#9ccc65 !important}.mdl-color--light-green-400{background-color:#9ccc65 !important}.mdl-color-text--light-green-500{color:#8bc34a !important}.mdl-color--light-green-500{background-color:#8bc34a !important}.mdl-color-text--light-green-600{color:#7cb342 !important}.mdl-color--light-green-600{background-color:#7cb342 !important}.mdl-color-text--light-green-700{color:#689f38 !important}.mdl-color--light-green-700{background-color:#689f38 !important}.mdl-color-text--light-green-800{color:#558b2f !important}.mdl-color--light-green-800{background-color:#558b2f !important}.mdl-color-text--light-green-900{color:#33691e !important}.mdl-color--light-green-900{background-color:#33691e !important}.mdl-color-text--light-green-A100{color:#ccff90 !important}.mdl-color--light-green-A100{background-color:#ccff90 !important}.mdl-color-text--light-green-A200{color:#b2ff59 !important}.mdl-color--light-green-A200{background-color:#b2ff59 !important}.mdl-color-text--light-green-A400{color:#76ff03 !important}.mdl-color--light-green-A400{background-color:#76ff03 !important}.mdl-color-text--light-green-A700{color:#64dd17 !important}.mdl-color--light-green-A700{background-color:#64dd17 !important}.mdl-color-text--lime{color:#cddc39 !important}.mdl-color--lime{background-color:#cddc39 !important}.mdl-color-text--lime-50{color:#f9fbe7 !important}.mdl-color--lime-50{background-color:#f9fbe7 !important}.mdl-color-text--lime-100{color:#f0f4c3 !important}.mdl-color--lime-100{background-color:#f0f4c3 !important}.mdl-color-text--lime-200{color:#e6ee9c !important}.mdl-color--lime-200{background-color:#e6ee9c !important}.mdl-color-text--lime-300{color:#dce775 !important}.mdl-color--lime-300{background-color:#dce775 !important}.mdl-color-text--lime-400{color:#d4e157 !important}.mdl-color--lime-400{background-color:#d4e157 !important}.mdl-color-text--lime-500{color:#cddc39 !important}.mdl-color--lime-500{background-color:#cddc39 !important}.mdl-color-text--lime-600{color:#c0ca33 !important}.mdl-color--lime-600{background-color:#c0ca33 !important}.mdl-color-text--lime-700{color:#afb42b !important}.mdl-color--lime-700{background-color:#afb42b !important}.mdl-color-text--lime-800{color:#9e9d24 !important}.mdl-color--lime-800{background-color:#9e9d24 !important}.mdl-color-text--lime-900{color:#827717 !important}.mdl-color--lime-900{background-color:#827717 !important}.mdl-color-text--lime-A100{color:#f4ff81 !important}.mdl-color--lime-A100{background-color:#f4ff81 !important}.mdl-color-text--lime-A200{color:#eeff41 !important}.mdl-color--lime-A200{background-color:#eeff41 !important}.mdl-color-text--lime-A400{color:#c6ff00 !important}.mdl-color--lime-A400{background-color:#c6ff00 !important}.mdl-color-text--lime-A700{color:#aeea00 !important}.mdl-color--lime-A700{background-color:#aeea00 !important}.mdl-color-text--yellow{color:#ffeb3b !important}.mdl-color--yellow{background-color:#ffeb3b !important}.mdl-color-text--yellow-50{color:#fffde7 !important}.mdl-color--yellow-50{background-color:#fffde7 !important}.mdl-color-text--yellow-100{color:#fff9c4 !important}.mdl-color--yellow-100{background-color:#fff9c4 !important}.mdl-color-text--yellow-200{color:#fff59d !important}.mdl-color--yellow-200{background-color:#fff59d !important}.mdl-color-text--yellow-300{color:#fff176 !important}.mdl-color--yellow-300{background-color:#fff176 !important}.mdl-color-text--yellow-400{color:#ffee58 !important}.mdl-color--yellow-400{background-color:#ffee58 !important}.mdl-color-text--yellow-500{color:#ffeb3b !important}.mdl-color--yellow-500{background-color:#ffeb3b !important}.mdl-color-text--yellow-600{color:#fdd835 !important}.mdl-color--yellow-600{background-color:#fdd835 !important}.mdl-color-text--yellow-700{color:#fbc02d !important}.mdl-color--yellow-700{background-color:#fbc02d !important}.mdl-color-text--yellow-800{color:#f9a825 !important}.mdl-color--yellow-800{background-color:#f9a825 !important}.mdl-color-text--yellow-900{color:#f57f17 !important}.mdl-color--yellow-900{background-color:#f57f17 !important}.mdl-color-text--yellow-A100{color:#ffff8d !important}.mdl-color--yellow-A100{background-color:#ffff8d !important}.mdl-color-text--yellow-A200{color:#ff0 !important}.mdl-color--yellow-A200{background-color:#ff0 !important}.mdl-color-text--yellow-A400{color:#ffea00 !important}.mdl-color--yellow-A400{background-color:#ffea00 !important}.mdl-color-text--yellow-A700{color:#ffd600 !important}.mdl-color--yellow-A700{background-color:#ffd600 !important}.mdl-color-text--amber{color:#ffc107 !important}.mdl-color--amber{background-color:#ffc107 !important}.mdl-color-text--amber-50{color:#fff8e1 !important}.mdl-color--amber-50{background-color:#fff8e1 !important}.mdl-color-text--amber-100{color:#ffecb3 !important}.mdl-color--amber-100{background-color:#ffecb3 !important}.mdl-color-text--amber-200{color:#ffe082 !important}.mdl-color--amber-200{background-color:#ffe082 !important}.mdl-color-text--amber-300{color:#ffd54f !important}.mdl-color--amber-300{background-color:#ffd54f !important}.mdl-color-text--amber-400{color:#ffca28 !important}.mdl-color--amber-400{background-color:#ffca28 !important}.mdl-color-text--amber-500{color:#ffc107 !important}.mdl-color--amber-500{background-color:#ffc107 !important}.mdl-color-text--amber-600{color:#ffb300 !important}.mdl-color--amber-600{background-color:#ffb300 !important}.mdl-color-text--amber-700{color:#ffa000 !important}.mdl-color--amber-700{background-color:#ffa000 !important}.mdl-color-text--amber-800{color:#ff8f00 !important}.mdl-color--amber-800{background-color:#ff8f00 !important}.mdl-color-text--amber-900{color:#ff6f00 !important}.mdl-color--amber-900{background-color:#ff6f00 !important}.mdl-color-text--amber-A100{color:#ffe57f !important}.mdl-color--amber-A100{background-color:#ffe57f !important}.mdl-color-text--amber-A200{color:#ffd740 !important}.mdl-color--amber-A200{background-color:#ffd740 !important}.mdl-color-text--amber-A400{color:#ffc400 !important}.mdl-color--amber-A400{background-color:#ffc400 !important}.mdl-color-text--amber-A700{color:#ffab00 !important}.mdl-color--amber-A700{background-color:#ffab00 !important}.mdl-color-text--orange{color:#ff9800 !important}.mdl-color--orange{background-color:#ff9800 !important}.mdl-color-text--orange-50{color:#fff3e0 !important}.mdl-color--orange-50{background-color:#fff3e0 !important}.mdl-color-text--orange-100{color:#ffe0b2 !important}.mdl-color--orange-100{background-color:#ffe0b2 !important}.mdl-color-text--orange-200{color:#ffcc80 !important}.mdl-color--orange-200{background-color:#ffcc80 !important}.mdl-color-text--orange-300{color:#ffb74d !important}.mdl-color--orange-300{background-color:#ffb74d !important}.mdl-color-text--orange-400{color:#ffa726 !important}.mdl-color--orange-400{background-color:#ffa726 !important}.mdl-color-text--orange-500{color:#ff9800 !important}.mdl-color--orange-500{background-color:#ff9800 !important}.mdl-color-text--orange-600{color:#fb8c00 !important}.mdl-color--orange-600{background-color:#fb8c00 !important}.mdl-color-text--orange-700{color:#f57c00 !important}.mdl-color--orange-700{background-color:#f57c00 !important}.mdl-color-text--orange-800{color:#ef6c00 !important}.mdl-color--orange-800{background-color:#ef6c00 !important}.mdl-color-text--orange-900{color:#e65100 !important}.mdl-color--orange-900{background-color:#e65100 !important}.mdl-color-text--orange-A100{color:#ffd180 !important}.mdl-color--orange-A100{background-color:#ffd180 !important}.mdl-color-text--orange-A200{color:#ffab40 !important}.mdl-color--orange-A200{background-color:#ffab40 !important}.mdl-color-text--orange-A400{color:#ff9100 !important}.mdl-color--orange-A400{background-color:#ff9100 !important}.mdl-color-text--orange-A700{color:#ff6d00 !important}.mdl-color--orange-A700{background-color:#ff6d00 !important}.mdl-color-text--deep-orange{color:#ff5722 !important}.mdl-color--deep-orange{background-color:#ff5722 !important}.mdl-color-text--deep-orange-50{color:#fbe9e7 !important}.mdl-color--deep-orange-50{background-color:#fbe9e7 !important}.mdl-color-text--deep-orange-100{color:#ffccbc !important}.mdl-color--deep-orange-100{background-color:#ffccbc !important}.mdl-color-text--deep-orange-200{color:#ffab91 !important}.mdl-color--deep-orange-200{background-color:#ffab91 !important}.mdl-color-text--deep-orange-300{color:#ff8a65 !important}.mdl-color--deep-orange-300{background-color:#ff8a65 !important}.mdl-color-text--deep-orange-400{color:#ff7043 !important}.mdl-color--deep-orange-400{background-color:#ff7043 !important}.mdl-color-text--deep-orange-500{color:#ff5722 !important}.mdl-color--deep-orange-500{background-color:#ff5722 !important}.mdl-color-text--deep-orange-600{color:#f4511e !important}.mdl-color--deep-orange-600{background-color:#f4511e !important}.mdl-color-text--deep-orange-700{color:#e64a19 !important}.mdl-color--deep-orange-700{background-color:#e64a19 !important}.mdl-color-text--deep-orange-800{color:#d84315 !important}.mdl-color--deep-orange-800{background-color:#d84315 !important}.mdl-color-text--deep-orange-900{color:#bf360c !important}.mdl-color--deep-orange-900{background-color:#bf360c !important}.mdl-color-text--deep-orange-A100{color:#ff9e80 !important}.mdl-color--deep-orange-A100{background-color:#ff9e80 !important}.mdl-color-text--deep-orange-A200{color:#ff6e40 !important}.mdl-color--deep-orange-A200{background-color:#ff6e40 !important}.mdl-color-text--deep-orange-A400{color:#ff3d00 !important}.mdl-color--deep-orange-A400{background-color:#ff3d00 !important}.mdl-color-text--deep-orange-A700{color:#dd2c00 !important}.mdl-color--deep-orange-A700{background-color:#dd2c00 !important}.mdl-color-text--brown{color:#795548 !important}.mdl-color--brown{background-color:#795548 !important}.mdl-color-text--brown-50{color:#efebe9 !important}.mdl-color--brown-50{background-color:#efebe9 !important}.mdl-color-text--brown-100{color:#d7ccc8 !important}.mdl-color--brown-100{background-color:#d7ccc8 !important}.mdl-color-text--brown-200{color:#bcaaa4 !important}.mdl-color--brown-200{background-color:#bcaaa4 !important}.mdl-color-text--brown-300{color:#a1887f !important}.mdl-color--brown-300{background-color:#a1887f !important}.mdl-color-text--brown-400{color:#8d6e63 !important}.mdl-color--brown-400{background-color:#8d6e63 !important}.mdl-color-text--brown-500{color:#795548 !important}.mdl-color--brown-500{background-color:#795548 !important}.mdl-color-text--brown-600{color:#6d4c41 !important}.mdl-color--brown-600{background-color:#6d4c41 !important}.mdl-color-text--brown-700{color:#5d4037 !important}.mdl-color--brown-700{background-color:#5d4037 !important}.mdl-color-text--brown-800{color:#4e342e !important}.mdl-color--brown-800{background-color:#4e342e !important}.mdl-color-text--brown-900{color:#3e2723 !important}.mdl-color--brown-900{background-color:#3e2723 !important}.mdl-color-text--grey{color:#9e9e9e !important}.mdl-color--grey{background-color:#9e9e9e !important}.mdl-color-text--grey-50{color:#fafafa !important}.mdl-color--grey-50{background-color:#fafafa !important}.mdl-color-text--grey-100{color:#f5f5f5 !important}.mdl-color--grey-100{background-color:#f5f5f5 !important}.mdl-color-text--grey-200{color:#eee !important}.mdl-color--grey-200{background-color:#eee !important}.mdl-color-text--grey-300{color:#e0e0e0 !important}.mdl-color--grey-300{background-color:#e0e0e0 !important}.mdl-color-text--grey-400{color:#bdbdbd !important}.mdl-color--grey-400{background-color:#bdbdbd !important}.mdl-color-text--grey-500{color:#9e9e9e !important}.mdl-color--grey-500{background-color:#9e9e9e !important}.mdl-color-text--grey-600{color:#757575 !important}.mdl-color--grey-600{background-color:#757575 !important}.mdl-color-text--grey-700{color:#616161 !important}.mdl-color--grey-700{background-color:#616161 !important}.mdl-color-text--grey-800{color:#424242 !important}.mdl-color--grey-800{background-color:#424242 !important}.mdl-color-text--grey-900{color:#212121 !important}.mdl-color--grey-900{background-color:#212121 !important}.mdl-color-text--blue-grey{color:#607d8b !important}.mdl-color--blue-grey{background-color:#607d8b !important}.mdl-color-text--blue-grey-50{color:#eceff1 !important}.mdl-color--blue-grey-50{background-color:#eceff1 !important}.mdl-color-text--blue-grey-100{color:#cfd8dc !important}.mdl-color--blue-grey-100{background-color:#cfd8dc !important}.mdl-color-text--blue-grey-200{color:#b0bec5 !important}.mdl-color--blue-grey-200{background-color:#b0bec5 !important}.mdl-color-text--blue-grey-300{color:#90a4ae !important}.mdl-color--blue-grey-300{background-color:#90a4ae !important}.mdl-color-text--blue-grey-400{color:#78909c !important}.mdl-color--blue-grey-400{background-color:#78909c !important}.mdl-color-text--blue-grey-500{color:#607d8b !important}.mdl-color--blue-grey-500{background-color:#607d8b !important}.mdl-color-text--blue-grey-600{color:#546e7a !important}.mdl-color--blue-grey-600{background-color:#546e7a !important}.mdl-color-text--blue-grey-700{color:#455a64 !important}.mdl-color--blue-grey-700{background-color:#455a64 !important}.mdl-color-text--blue-grey-800{color:#37474f !important}.mdl-color--blue-grey-800{background-color:#37474f !important}.mdl-color-text--blue-grey-900{color:#263238 !important}.mdl-color--blue-grey-900{background-color:#263238 !important}.mdl-color--black{background-color:#000 !important}.mdl-color-text--black{color:#000 !important}.mdl-color--white{background-color:#fff !important}.mdl-color-text--white{color:#fff !important}.mdl-color--primary{background-color:rgb(244,67,54)!important}.mdl-color--primary-contrast{background-color:rgb(255,255,255)!important}.mdl-color--primary-dark{background-color:rgb(211,47,47)!important}.mdl-color--accent{background-color:rgb(100,255,218)!important}.mdl-color--accent-contrast{background-color:rgb(66,66,66)!important}.mdl-color-text--primary{color:rgb(244,67,54)!important}.mdl-color-text--primary-contrast{color:rgb(255,255,255)!important}.mdl-color-text--primary-dark{color:rgb(211,47,47)!important}.mdl-color-text--accent{color:rgb(100,255,218)!important}.mdl-color-text--accent-contrast{color:rgb(66,66,66)!important}.mdl-ripple{background:#000;border-radius:50%;height:50px;left:0;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:50px;overflow:hidden}.mdl-ripple.is-animating{transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1);transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1),-webkit-transform .3s cubic-bezier(0,0,.2,1)}.mdl-ripple.is-visible{opacity:.3}.mdl-animation--default,.mdl-animation--fast-out-slow-in{transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-animation--linear-out-slow-in{transition-timing-function:cubic-bezier(0,0,.2,1)}.mdl-animation--fast-out-linear-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.mdl-badge{position:relative;white-space:nowrap;margin-right:24px}.mdl-badge:not([data-badge]){margin-right:auto}.mdl-badge[data-badge]:after{content:attr(data-badge);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:absolute;top:-11px;right:-24px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:600;font-size:12px;width:22px;height:22px;border-radius:50%;background:rgb(100,255,218);color:rgb(66,66,66)}.mdl-button .mdl-badge[data-badge]:after{top:-10px;right:-5px}.mdl-badge.mdl-badge--no-background[data-badge]:after{color:rgb(100,255,218);background:rgba(66,66,66,.2);box-shadow:0 0 1px gray}.mdl-badge.mdl-badge--overlap{margin-right:10px}.mdl-badge.mdl-badge--overlap:after{right:-10px}.mdl-button{background:0 0;border:none;border-radius:2px;color:#000;position:relative;height:36px;margin:0;min-width:64px;padding:0 16px;display:inline-block;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;letter-spacing:0;overflow:hidden;will-change:box-shadow;transition:box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);outline:none;cursor:pointer;text-decoration:none;text-align:center;line-height:36px;vertical-align:middle}.mdl-button::-moz-focus-inner{border:0}.mdl-button:hover{background-color:rgba(158,158,158,.2)}.mdl-button:focus:not(:active){background-color:rgba(0,0,0,.12)}.mdl-button:active{background-color:rgba(158,158,158,.4)}.mdl-button.mdl-button--colored{color:rgb(244,67,54)}.mdl-button.mdl-button--colored:focus:not(:active){background-color:rgba(0,0,0,.12)}input.mdl-button[type="submit"]{-webkit-appearance:none}.mdl-button--raised{background:rgba(158,158,158,.2);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-button--raised:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--raised:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--raised.mdl-button--colored{background:rgb(244,67,54);color:rgb(255,255,255)}.mdl-button--raised.mdl-button--colored:hover{background-color:rgb(244,67,54)}.mdl-button--raised.mdl-button--colored:active{background-color:rgb(244,67,54)}.mdl-button--raised.mdl-button--colored:focus:not(:active){background-color:rgb(244,67,54)}.mdl-button--raised.mdl-button--colored .mdl-ripple{background:rgb(255,255,255)}.mdl-button--fab{border-radius:50%;font-size:24px;height:56px;margin:auto;min-width:56px;width:56px;padding:0;overflow:hidden;background:rgba(158,158,158,.2);box-shadow:0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);position:relative;line-height:normal}.mdl-button--fab .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--fab.mdl-button--mini-fab{height:40px;min-width:40px;width:40px}.mdl-button--fab .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button--fab:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--fab:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--fab.mdl-button--colored{background:rgb(100,255,218);color:rgb(66,66,66)}.mdl-button--fab.mdl-button--colored:hover{background-color:rgb(100,255,218)}.mdl-button--fab.mdl-button--colored:focus:not(:active){background-color:rgb(100,255,218)}.mdl-button--fab.mdl-button--colored:active{background-color:rgb(100,255,218)}.mdl-button--fab.mdl-button--colored .mdl-ripple{background:rgb(66,66,66)}.mdl-button--icon{border-radius:50%;font-size:24px;height:32px;margin-left:0;margin-right:0;min-width:32px;width:32px;padding:0;overflow:hidden;color:inherit;line-height:normal}.mdl-button--icon .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon{height:24px;min-width:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon .material-icons{top:0;left:0}.mdl-button--icon .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button__ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-button[disabled] .mdl-button__ripple-container .mdl-ripple,.mdl-button.mdl-button--disabled .mdl-button__ripple-container .mdl-ripple{background-color:transparent}.mdl-button--primary.mdl-button--primary{color:rgb(244,67,54)}.mdl-button--primary.mdl-button--primary .mdl-ripple{background:rgb(255,255,255)}.mdl-button--primary.mdl-button--primary.mdl-button--raised,.mdl-button--primary.mdl-button--primary.mdl-button--fab{color:rgb(255,255,255);background-color:rgb(244,67,54)}.mdl-button--accent.mdl-button--accent{color:rgb(100,255,218)}.mdl-button--accent.mdl-button--accent .mdl-ripple{background:rgb(66,66,66)}.mdl-button--accent.mdl-button--accent.mdl-button--raised,.mdl-button--accent.mdl-button--accent.mdl-button--fab{color:rgb(66,66,66);background-color:rgb(100,255,218)}.mdl-button[disabled][disabled],.mdl-button.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26);cursor:default;background-color:transparent}.mdl-button--fab[disabled][disabled],.mdl-button--fab.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-button--raised[disabled][disabled],.mdl-button--raised.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26);box-shadow:none}.mdl-button--colored[disabled][disabled],.mdl-button--colored.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26)}.mdl-button .material-icons{vertical-align:middle}.mdl-card{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;font-size:16px;font-weight:400;min-height:200px;overflow:hidden;width:330px;z-index:1;position:relative;background:#fff;border-radius:2px;box-sizing:border-box}.mdl-card__media{background-color:rgb(100,255,218);background-repeat:repeat;background-position:50% 50%;background-size:cover;background-origin:padding-box;background-attachment:scroll;box-sizing:border-box}.mdl-card__title{-webkit-align-items:center;-ms-flex-align:center;align-items:center;color:#000;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;line-height:normal;padding:16px;-webkit-perspective-origin:165px 56px;perspective-origin:165px 56px;-webkit-transform-origin:165px 56px;transform-origin:165px 56px;box-sizing:border-box}.mdl-card__title.mdl-card--border{border-bottom:1px solid rgba(0,0,0,.1)}.mdl-card__title-text{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;color:inherit;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:24px;font-weight:300;line-height:normal;overflow:hidden;-webkit-transform-origin:149px 48px;transform-origin:149px 48px;margin:0}.mdl-card__subtitle-text{font-size:14px;color:rgba(0,0,0,.54);margin:0}.mdl-card__supporting-text{color:rgba(0,0,0,.54);font-size:1rem;line-height:18px;overflow:hidden;padding:16px;width:90%}.mdl-card__actions{font-size:16px;line-height:normal;width:100%;background-color:transparent;padding:8px;box-sizing:border-box}.mdl-card__actions.mdl-card--border{border-top:1px solid rgba(0,0,0,.1)}.mdl-card--expand{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-card__menu{position:absolute;right:16px;top:16px}.mdl-checkbox{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0}.mdl-checkbox.is-upgraded{padding-left:24px}.mdl-checkbox__input{line-height:24px}.mdl-checkbox.is-upgraded .mdl-checkbox__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-checkbox__box-outline{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;overflow:hidden;border:2px solid rgba(0,0,0,.54);border-radius:2px;z-index:2}.mdl-checkbox.is-checked .mdl-checkbox__box-outline{border:2px solid rgb(244,67,54)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__box-outline,.mdl-checkbox.is-disabled .mdl-checkbox__box-outline{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__focus-helper{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:transparent}.mdl-checkbox.is-focused .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-checkbox.is-focused.is-checked .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(244,67,54,.26);background-color:rgba(244,67,54,.26)}.mdl-checkbox__tick-outline{position:absolute;top:0;left:0;height:100%;width:100%;-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");background:0 0;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background}.mdl-checkbox.is-checked .mdl-checkbox__tick-outline{background:rgb(244,67,54)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}fieldset[disabled] .mdl-checkbox.is-checked .mdl-checkbox__tick-outline,.mdl-checkbox.is-checked.is-disabled .mdl-checkbox__tick-outline{background:rgba(0,0,0,.26)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}.mdl-checkbox__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0}fieldset[disabled] .mdl-checkbox .mdl-checkbox__label,.mdl-checkbox.is-disabled .mdl-checkbox__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__ripple-container{position:absolute;z-index:2;top:-6px;left:-10px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-checkbox__ripple-container .mdl-ripple{background:rgb(244,67,54)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container{cursor:auto}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container .mdl-ripple,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container .mdl-ripple{background:0 0}.mdl-chip{height:32px;font-family:"Roboto","Helvetica","Arial",sans-serif;line-height:32px;padding:0 12px;border:0;border-radius:16px;background-color:#dedede;display:inline-block;color:rgba(0,0,0,.87);margin:2px 0;font-size:0;white-space:nowrap}.mdl-chip__text{font-size:13px;vertical-align:middle;display:inline-block}.mdl-chip__action{height:24px;width:24px;background:0 0;opacity:.54;cursor:pointer;padding:0;margin:0 0 0 4px;font-size:13px;text-decoration:none;color:rgba(0,0,0,.87);border:none;outline:none}.mdl-chip__action,.mdl-chip__contact{display:inline-block;vertical-align:middle;overflow:hidden;text-align:center}.mdl-chip__contact{height:32px;width:32px;border-radius:16px;margin-right:8px;font-size:18px;line-height:32px}.mdl-chip:focus{outline:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-chip:active{background-color:#d6d6d6}.mdl-chip--deletable{padding-right:4px}.mdl-chip--contact{padding-left:0}.mdl-data-table{position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff}.mdl-data-table thead{padding-bottom:3px}.mdl-data-table thead .mdl-data-table__select{margin-top:0}.mdl-data-table tbody tr{position:relative;height:48px;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background-color}.mdl-data-table tbody tr.is-selected{background-color:#e0e0e0}.mdl-data-table tbody tr:hover{background-color:#eee}.mdl-data-table td{text-align:right}.mdl-data-table th{padding:0 18px 12px 18px;text-align:right}.mdl-data-table td:first-of-type,.mdl-data-table th:first-of-type{padding-left:24px}.mdl-data-table td:last-of-type,.mdl-data-table th:last-of-type{padding-right:24px}.mdl-data-table td{position:relative;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px;box-sizing:border-box}.mdl-data-table td,.mdl-data-table td .mdl-data-table__select{vertical-align:middle}.mdl-data-table th{position:relative;vertical-align:bottom;text-overflow:ellipsis;font-weight:700;line-height:24px;letter-spacing:0;height:48px;font-size:12px;color:rgba(0,0,0,.54);padding-bottom:8px;box-sizing:border-box}.mdl-data-table th.mdl-data-table__header--sorted-ascending,.mdl-data-table th.mdl-data-table__header--sorted-descending{color:rgba(0,0,0,.87)}.mdl-data-table th.mdl-data-table__header--sorted-ascending:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:before{font-family:'Material Icons';font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased;font-size:16px;content:"\e5d8";margin-right:5px;vertical-align:sub}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover{cursor:pointer}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover:before{color:rgba(0,0,0,.26)}.mdl-data-table th.mdl-data-table__header--sorted-descending:before{content:"\e5db"}.mdl-data-table__select{width:16px}.mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric{text-align:left}.mdl-dialog{border:none;box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2);width:280px}.mdl-dialog__title{padding:24px 24px 0;margin:0;font-size:2.5rem}.mdl-dialog__actions{padding:8px 8px 8px 24px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.mdl-dialog__actions>*{margin-right:8px;height:36px}.mdl-dialog__actions>*:first-child{margin-right:0}.mdl-dialog__actions--full-width{padding:0 0 8px}.mdl-dialog__actions--full-width>*{height:48px;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;padding-right:16px;margin-right:0;text-align:right}.mdl-dialog__content{padding:20px 24px 24px;color:rgba(0,0,0,.54)}.mdl-mega-footer{padding:16px 40px;color:#9e9e9e;background-color:#424242}.mdl-mega-footer--top-section:after,.mdl-mega-footer--middle-section:after,.mdl-mega-footer--bottom-section:after,.mdl-mega-footer__top-section:after,.mdl-mega-footer__middle-section:after,.mdl-mega-footer__bottom-section:after{content:'';display:block;clear:both}.mdl-mega-footer--left-section,.mdl-mega-footer__left-section,.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{margin-bottom:16px}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:block;margin-bottom:16px;color:inherit;text-decoration:none}@media screen and (min-width:760px){.mdl-mega-footer--left-section,.mdl-mega-footer__left-section{float:left}.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{float:right}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:inline-block;margin-left:16px;line-height:36px;vertical-align:middle}}.mdl-mega-footer--social-btn,.mdl-mega-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{display:block;position:relative}@media screen and (min-width:760px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{width:33%}.mdl-mega-footer--drop-down-section:nth-child(1),.mdl-mega-footer--drop-down-section:nth-child(2),.mdl-mega-footer__drop-down-section:nth-child(1),.mdl-mega-footer__drop-down-section:nth-child(2){float:left}.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(3){float:right}.mdl-mega-footer--drop-down-section:nth-child(3):after,.mdl-mega-footer__drop-down-section:nth-child(3):after{clear:right}.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section:nth-child(4){clear:right;float:right}.mdl-mega-footer--middle-section:after,.mdl-mega-footer__middle-section:after{content:'';display:block;clear:both}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:0}}@media screen and (min-width:1024px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section,.mdl-mega-footer__drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(4){width:24%;float:left}}.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{position:absolute;width:100%;height:55.8px;padding:32px;margin:-16px 0 0;cursor:pointer;z-index:1;opacity:0}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CE'}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list{display:none}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CF'}.mdl-mega-footer--heading,.mdl-mega-footer__heading{position:relative;width:100%;padding-right:39.8px;margin-bottom:16px;box-sizing:border-box;font-size:14px;line-height:23.8px;font-weight:500;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#e0e0e0}.mdl-mega-footer--heading:after,.mdl-mega-footer__heading:after{content:'';position:absolute;top:0;right:0;display:block;width:23.8px;height:23.8px;background-size:cover}.mdl-mega-footer--link-list,.mdl-mega-footer__link-list{list-style:none;padding:0;margin:0 0 32px}.mdl-mega-footer--link-list:after,.mdl-mega-footer__link-list:after{clear:both;display:block;content:''}.mdl-mega-footer--link-list li,.mdl-mega-footer__link-list li{font-size:14px;font-weight:400;letter-spacing:0;line-height:20px}.mdl-mega-footer--link-list a,.mdl-mega-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}@media screen and (min-width:760px){.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{display:none}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{content:''}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list{display:block}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{content:''}}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:16px;margin-bottom:16px}.mdl-logo{margin-bottom:16px;color:#fff}.mdl-mega-footer--bottom-section .mdl-mega-footer--link-list li,.mdl-mega-footer__bottom-section .mdl-mega-footer__link-list li{float:left;margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-logo{float:left;margin-bottom:0;margin-right:16px}}.mdl-mini-footer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:32px 16px;color:#9e9e9e;background-color:#424242}.mdl-mini-footer:after{content:'';display:block}.mdl-mini-footer .mdl-logo{line-height:36px}.mdl-mini-footer--link-list,.mdl-mini-footer__link-list{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;list-style:none;margin:0;padding:0}.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{line-height:36px}}.mdl-mini-footer--link-list a,.mdl-mini-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}.mdl-mini-footer--left-section,.mdl-mini-footer__left-section{display:inline-block;-webkit-order:0;-ms-flex-order:0;order:0}.mdl-mini-footer--right-section,.mdl-mini-footer__right-section{display:inline-block;-webkit-order:1;-ms-flex-order:1;order:1}.mdl-mini-footer--social-btn,.mdl-mini-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-icon-toggle{position:relative;z-index:1;vertical-align:middle;display:inline-block;height:32px;margin:0;padding:0}.mdl-icon-toggle__input{line-height:32px}.mdl-icon-toggle.is-upgraded .mdl-icon-toggle__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-icon-toggle__label{display:inline-block;position:relative;cursor:pointer;height:32px;width:32px;min-width:32px;color:#616161;border-radius:50%;padding:0;margin-left:0;margin-right:0;text-align:center;background-color:transparent;will-change:background-color;transition:background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1)}.mdl-icon-toggle__label.material-icons{line-height:32px;font-size:24px}.mdl-icon-toggle.is-checked .mdl-icon-toggle__label{color:rgb(244,67,54)}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__label{color:rgba(0,0,0,.26);cursor:auto;transition:none}.mdl-icon-toggle.is-focused .mdl-icon-toggle__label{background-color:rgba(0,0,0,.12)}.mdl-icon-toggle.is-focused.is-checked .mdl-icon-toggle__label{background-color:rgba(244,67,54,.26)}.mdl-icon-toggle__ripple-container{position:absolute;z-index:2;top:-2px;left:-2px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-icon-toggle__ripple-container .mdl-ripple{background:#616161}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container{cursor:auto}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container .mdl-ripple{background:0 0}.mdl-list{display:block;padding:8px 0;list-style:none}.mdl-list__item{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:.04em;line-height:1;min-height:48px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;padding:16px;cursor:default;color:rgba(0,0,0,.87);overflow:hidden}.mdl-list__item,.mdl-list__item .mdl-list__item-primary-content{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.mdl-list__item .mdl-list__item-primary-content{-webkit-order:0;-ms-flex-order:0;order:0;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2;text-decoration:none}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-icon{margin-right:32px}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-avatar{margin-right:16px}.mdl-list__item .mdl-list__item-secondary-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column;-ms-flex-flow:column;flex-flow:column;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;margin-left:16px}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-action label{display:inline}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-info{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;color:rgba(0,0,0,.54)}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-sub-header{padding:0 0 0 16px}.mdl-list__item-icon,.mdl-list__item-icon.material-icons{height:24px;width:24px;font-size:24px;box-sizing:border-box;color:#757575}.mdl-list__item-avatar,.mdl-list__item-avatar.material-icons{height:40px;width:40px;box-sizing:border-box;border-radius:50%;background-color:#757575;font-size:40px;color:#fff}.mdl-list__item--two-line{height:72px}.mdl-list__item--two-line .mdl-list__item-primary-content{height:36px;line-height:20px;display:block}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-avatar{float:left}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left;margin-top:6px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-secondary-content{height:36px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-sub-title{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-list__item--three-line{height:88px}.mdl-list__item--three-line .mdl-list__item-primary-content{height:52px;line-height:20px;display:block}.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-avatar,.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left}.mdl-list__item--three-line .mdl-list__item-secondary-content{height:52px}.mdl-list__item--three-line .mdl-list__item-text-body{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;height:52px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-menu__container{display:block;margin:0;padding:0;border:none;position:absolute;overflow:visible;height:0;width:0;visibility:hidden;z-index:-1}.mdl-menu__container.is-visible,.mdl-menu__container.is-animating{z-index:999;visibility:visible}.mdl-menu__outline{display:block;background:#fff;margin:0;padding:0;border:none;border-radius:2px;position:absolute;top:0;left:0;overflow:hidden;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;transform-origin:0 0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);will-change:transform;transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1);z-index:-1}.mdl-menu__container.is-visible .mdl-menu__outline{opacity:1;-webkit-transform:scale(1);transform:scale(1);z-index:999}.mdl-menu__outline.mdl-menu--bottom-right{-webkit-transform-origin:100% 0;transform-origin:100% 0}.mdl-menu__outline.mdl-menu--top-left{-webkit-transform-origin:0 100%;transform-origin:0 100%}.mdl-menu__outline.mdl-menu--top-right{-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.mdl-menu{position:absolute;list-style:none;top:0;left:0;height:auto;width:auto;min-width:124px;padding:8px 0;margin:0;opacity:0;clip:rect(0 0 0 0);z-index:-1}.mdl-menu__container.is-visible .mdl-menu{opacity:1;z-index:999}.mdl-menu.is-animating{transition:opacity .2s cubic-bezier(.4,0,.2,1),clip .3s cubic-bezier(.4,0,.2,1)}.mdl-menu.mdl-menu--bottom-right{left:auto;right:0}.mdl-menu.mdl-menu--top-left{top:auto;bottom:0}.mdl-menu.mdl-menu--top-right{top:auto;left:auto;bottom:0;right:0}.mdl-menu.mdl-menu--unaligned{top:auto;left:auto}.mdl-menu__item{display:block;border:none;color:rgba(0,0,0,.87);background-color:transparent;text-align:left;margin:0;padding:0 16px;outline-color:#bdbdbd;position:relative;overflow:hidden;font-size:14px;font-weight:400;letter-spacing:0;text-decoration:none;cursor:pointer;height:48px;line-height:48px;white-space:nowrap;opacity:0;transition:opacity .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-menu__container.is-visible .mdl-menu__item{opacity:1}.mdl-menu__item::-moz-focus-inner{border:0}.mdl-menu__item--full-bleed-divider{border-bottom:1px solid rgba(0,0,0,.12)}.mdl-menu__item[disabled],.mdl-menu__item[data-mdl-disabled]{color:#bdbdbd;background-color:transparent;cursor:auto}.mdl-menu__item[disabled]:hover,.mdl-menu__item[data-mdl-disabled]:hover{background-color:transparent}.mdl-menu__item[disabled]:focus,.mdl-menu__item[data-mdl-disabled]:focus{background-color:transparent}.mdl-menu__item[disabled] .mdl-ripple,.mdl-menu__item[data-mdl-disabled] .mdl-ripple{background:0 0}.mdl-menu__item:hover{background-color:#eee}.mdl-menu__item:focus{outline:none;background-color:#eee}.mdl-menu__item:active{background-color:#e0e0e0}.mdl-menu__item--ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-progress{display:block;position:relative;height:4px;width:500px;max-width:100%}.mdl-progress>.bar{display:block;position:absolute;top:0;bottom:0;width:0%;transition:width .2s cubic-bezier(.4,0,.2,1)}.mdl-progress>.progressbar{background-color:rgb(244,67,54);z-index:1;left:0}.mdl-progress>.bufferbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(244,67,54),rgb(244,67,54));z-index:0;left:0}.mdl-progress>.auxbar{right:0}@supports (-webkit-appearance:none){.mdl-progress:not(.mdl-progress--indeterminate):not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate):not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(244,67,54),rgb(244,67,54));-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=")}}.mdl-progress:not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.9),rgba(255,255,255,.9)),linear-gradient(to right,rgb(244,67,54),rgb(244,67,54))}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1{-webkit-animation-name:indeterminate1;animation-name:indeterminate1}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1,.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-color:rgb(244,67,54);-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-image:none;-webkit-animation-name:indeterminate2;animation-name:indeterminate2}@-webkit-keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@-webkit-keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}@keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}.mdl-navigation{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;box-sizing:border-box}.mdl-navigation__link{color:#424242;text-decoration:none;margin:0;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0;opacity:.87}.mdl-navigation__link .material-icons{vertical-align:middle}.mdl-layout{width:100%;height:100%;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow-y:auto;overflow-x:hidden;position:relative;-webkit-overflow-scrolling:touch}.mdl-layout.is-small-screen .mdl-layout--large-screen-only{display:none}.mdl-layout:not(.is-small-screen) .mdl-layout--small-screen-only{display:none}.mdl-layout__container{position:absolute;width:100%;height:100%}.mdl-layout__title,.mdl-layout-title{display:block;position:relative;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400;box-sizing:border-box}.mdl-layout-spacer{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-layout__drawer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:240px;height:100%;max-height:100%;position:absolute;top:0;left:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);box-sizing:border-box;border-right:1px solid #e0e0e0;background:#fafafa;-webkit-transform:translateX(-250px);transform:translateX(-250px);-webkit-transform-style:preserve-3d;transform-style:preserve-3d;will-change:transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;color:#424242;overflow:visible;overflow-y:auto;z-index:5}.mdl-layout__drawer.is-visible{-webkit-transform:translateX(0);transform:translateX(0)}.mdl-layout__drawer.is-visible~.mdl-layout__content.mdl-layout__content{overflow:hidden}.mdl-layout__drawer>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:64px;padding-left:40px}@media screen and (max-width:1024px){.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:56px;padding-left:16px}}.mdl-layout__drawer .mdl-navigation{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-ms-flex-align:stretch;-ms-grid-row-align:stretch;align-items:stretch;padding-top:16px}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{display:block;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;padding:16px 40px;margin:0;color:#757575}@media screen and (max-width:1024px){.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{padding:16px}}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link:hover{background-color:#e0e0e0}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link--current{background-color:#e0e0e0;color:#000}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__drawer{-webkit-transform:translateX(0);transform:translateX(0)}}.mdl-layout__drawer-button{display:block;position:absolute;height:48px;width:48px;border:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden;text-align:center;cursor:pointer;font-size:26px;line-height:56px;font-family:Helvetica,Arial,sans-serif;margin:8px 12px;top:0;left:0;color:rgb(255,255,255);z-index:4}.mdl-layout__header .mdl-layout__drawer-button{position:absolute;color:rgb(255,255,255);background-color:inherit}@media screen and (max-width:1024px){.mdl-layout__header .mdl-layout__drawer-button{margin:4px}}@media screen and (max-width:1024px){.mdl-layout__drawer-button{margin:4px;color:rgba(0,0,0,.5)}}@media screen and (min-width:1025px){.mdl-layout__drawer-button{line-height:54px}.mdl-layout--no-desktop-drawer-button .mdl-layout__drawer-button,.mdl-layout--fixed-drawer>.mdl-layout__drawer-button,.mdl-layout--no-drawer-button .mdl-layout__drawer-button{display:none}}.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;box-sizing:border-box;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;width:100%;margin:0;padding:0;border:none;min-height:64px;max-height:1000px;z-index:3;background-color:rgb(244,67,54);color:rgb(255,255,255);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:max-height,box-shadow}@media screen and (max-width:1024px){.mdl-layout__header{min-height:56px}}.mdl-layout--fixed-drawer.is-upgraded:not(.is-small-screen)>.mdl-layout__header{margin-left:240px;width:calc(100% - 240px)}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__header .mdl-layout__header-row{padding-left:40px}}.mdl-layout__header>.mdl-layout-icon{position:absolute;left:40px;top:16px;height:32px;width:32px;overflow:hidden;z-index:3;display:block}@media screen and (max-width:1024px){.mdl-layout__header>.mdl-layout-icon{left:16px;top:12px}}.mdl-layout.has-drawer .mdl-layout__header>.mdl-layout-icon{display:none}.mdl-layout__header.is-compact{max-height:64px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact{max-height:56px}}.mdl-layout__header.is-compact.has-tabs{height:112px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact.has-tabs{min-height:104px}}@media screen and (max-width:1024px){.mdl-layout__header{display:none}.mdl-layout--fixed-header>.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex}}.mdl-layout__header--transparent.mdl-layout__header--transparent{background-color:transparent;box-shadow:none}.mdl-layout__header--seamed,.mdl-layout__header--scroll{box-shadow:none}.mdl-layout__header--waterfall{box-shadow:none;overflow:hidden}.mdl-layout__header--waterfall.is-casting-shadow{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-layout__header--waterfall.mdl-layout__header--waterfall-hide-top{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.mdl-layout__header-row{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:64px;margin:0;padding:0 40px 0 80px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:40px}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__header-row{padding-left:40px}}@media screen and (max-width:1024px){.mdl-layout__header-row{height:56px;padding:0 16px 0 72px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:16px}}.mdl-layout__header-row>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__header--scroll .mdl-layout__header-row{width:100%}.mdl-layout__header-row .mdl-navigation{margin:0;padding:0;height:64px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation{height:56px}}.mdl-layout__header-row .mdl-navigation__link{display:block;color:rgb(255,255,255);line-height:64px;padding:0 24px}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation__link{line-height:56px;padding:0 16px}}.mdl-layout__obfuscator{background-color:transparent;position:absolute;top:0;left:0;height:100%;width:100%;z-index:4;visibility:hidden;transition-property:background-color;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-layout__obfuscator.is-visible{background-color:rgba(0,0,0,.5);visibility:visible}@supports (pointer-events:auto){.mdl-layout__obfuscator{background-color:rgba(0,0,0,.5);opacity:0;transition-property:opacity;visibility:visible;pointer-events:none}.mdl-layout__obfuscator.is-visible{pointer-events:auto;opacity:1}}.mdl-layout__content{-ms-flex:0 1 auto;position:relative;display:inline-block;overflow-y:auto;overflow-x:hidden;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;z-index:1;-webkit-overflow-scrolling:touch}.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:240px}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow:visible}@media screen and (max-width:1024px){.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:0}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow-y:auto;overflow-x:hidden}}.mdl-layout__tab-bar{height:96px;margin:0;width:calc(100% - 112px);padding:0 0 0 56px;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:rgb(244,67,54);overflow-y:hidden;overflow-x:scroll}.mdl-layout__tab-bar::-webkit-scrollbar{display:none}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}}@media screen and (max-width:1024px){.mdl-layout__tab-bar{width:calc(100% - 60px);padding:0 0 0 60px}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{width:calc(100% - 8px);padding-left:4px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar{padding:0;overflow:hidden;width:100%}.mdl-layout__tab-bar-container{position:relative;height:48px;width:100%;border:none;margin:0;z-index:2;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden}.mdl-layout__container>.mdl-layout__tab-bar-container{position:absolute;top:0;left:0}.mdl-layout__tab-bar-button{display:inline-block;position:absolute;top:0;height:48px;width:56px;z-index:4;text-align:center;background-color:rgb(244,67,54);color:transparent;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button{width:16px}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button .material-icons,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button .material-icons{position:relative;left:-4px}@media screen and (max-width:1024px){.mdl-layout__tab-bar-button{width:60px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar-button{display:none}.mdl-layout__tab-bar-button .material-icons{line-height:48px}.mdl-layout__tab-bar-button.is-active{color:rgb(255,255,255)}.mdl-layout__tab-bar-left-button{left:0}.mdl-layout__tab-bar-right-button{right:0}.mdl-layout__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(255,255,255,.6);overflow:hidden}@media screen and (max-width:1024px){.mdl-layout__tab{padding:0 12px}}.mdl-layout--fixed-tabs .mdl-layout__tab{float:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;padding:0}.mdl-layout.is-upgraded .mdl-layout__tab.is-active{color:rgb(255,255,255)}.mdl-layout.is-upgraded .mdl-layout__tab.is-active::after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(100,255,218);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-layout__tab .mdl-layout__tab-ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-layout__tab .mdl-layout__tab-ripple-container .mdl-ripple{background-color:rgb(255,255,255)}.mdl-layout__tab-panel{display:block}.mdl-layout.is-upgraded .mdl-layout__tab-panel{display:none}.mdl-layout.is-upgraded .mdl-layout__tab-panel.is-active{display:block}.mdl-radio{position:relative;font-size:16px;line-height:24px;display:inline-block;box-sizing:border-box;margin:0;padding-left:0}.mdl-radio.is-upgraded{padding-left:24px}.mdl-radio__button{line-height:24px}.mdl-radio.is-upgraded .mdl-radio__button{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-radio__outer-circle{position:absolute;top:4px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;border:2px solid rgba(0,0,0,.54);border-radius:50%;z-index:2}.mdl-radio.is-checked .mdl-radio__outer-circle{border:2px solid rgb(244,67,54)}.mdl-radio__outer-circle fieldset[disabled] .mdl-radio,.mdl-radio.is-disabled .mdl-radio__outer-circle{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-radio__inner-circle{position:absolute;z-index:1;margin:0;top:8px;left:4px;box-sizing:border-box;width:8px;height:8px;cursor:pointer;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;-webkit-transform:scale3d(0,0,0);transform:scale3d(0,0,0);border-radius:50%;background:rgb(244,67,54)}.mdl-radio.is-checked .mdl-radio__inner-circle{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}fieldset[disabled] .mdl-radio .mdl-radio__inner-circle,.mdl-radio.is-disabled .mdl-radio__inner-circle{background:rgba(0,0,0,.26);cursor:auto}.mdl-radio.is-focused .mdl-radio__inner-circle{box-shadow:0 0 0 10px rgba(0,0,0,.1)}.mdl-radio__label{cursor:pointer}fieldset[disabled] .mdl-radio .mdl-radio__label,.mdl-radio.is-disabled .mdl-radio__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-radio__ripple-container{position:absolute;z-index:2;top:-9px;left:-13px;box-sizing:border-box;width:42px;height:42px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-radio__ripple-container .mdl-ripple{background:rgb(244,67,54)}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container,.mdl-radio.is-disabled .mdl-radio__ripple-container{cursor:auto}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container .mdl-ripple,.mdl-radio.is-disabled .mdl-radio__ripple-container .mdl-ripple{background:0 0}_:-ms-input-placeholder,:root .mdl-slider.mdl-slider.is-upgraded{-ms-appearance:none;height:32px;margin:0}.mdl-slider{width:calc(100% - 40px);margin:0 20px}.mdl-slider.is-upgraded{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:2px;background:0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:0;padding:0;color:rgb(244,67,54);-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;z-index:1;cursor:pointer}.mdl-slider.is-upgraded::-moz-focus-outer{border:0}.mdl-slider.is-upgraded::-ms-tooltip{display:none}.mdl-slider.is-upgraded::-webkit-slider-runnable-track{background:0 0}.mdl-slider.is-upgraded::-moz-range-track{background:0 0;border:none}.mdl-slider.is-upgraded::-ms-track{background:0 0;color:transparent;height:2px;width:100%;border:none}.mdl-slider.is-upgraded::-ms-fill-lower{padding:0;background:linear-gradient(to right,transparent,transparent 16px,rgb(244,67,54)16px,rgb(244,67,54)0)}.mdl-slider.is-upgraded::-ms-fill-upper{padding:0;background:linear-gradient(to left,transparent,transparent 16px,rgba(0,0,0,.26)16px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background:rgb(244,67,54);border:none;transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded::-moz-range-thumb{-moz-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background-image:none;background:rgb(244,67,54);border:none}.mdl-slider.is-upgraded:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(244,67,54,.26)}.mdl-slider.is-upgraded:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(244,67,54,.26)}.mdl-slider.is-upgraded:active::-webkit-slider-thumb{background-image:none;background:rgb(244,67,54);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded:active::-moz-range-thumb{background-image:none;background:rgb(244,67,54);transform:scale(1.5)}.mdl-slider.is-upgraded::-ms-thumb{width:32px;height:32px;border:none;border-radius:50%;background:rgb(244,67,54);transform:scale(.375);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgb(244,67,54)0%,rgb(244,67,54)37.5%,rgba(244,67,54,.26)37.5%,rgba(244,67,54,.26)100%);transform:scale(1)}.mdl-slider.is-upgraded:active::-ms-thumb{background:rgb(244,67,54);transform:scale(.5625)}.mdl-slider.is-upgraded.is-lowest-value::-webkit-slider-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-moz-range-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:active::-webkit-slider-thumb{border:1.6px solid rgba(0,0,0,.26);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:9px}.mdl-slider.is-upgraded.is-lowest-value:active::-moz-range-thumb{border:1.5px solid rgba(0,0,0,.26);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value::-ms-thumb{background:radial-gradient(circle closest-side,transparent 0%,transparent 66.67%,rgba(0,0,0,.26)66.67%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgba(0,0,0,.12)0%,rgba(0,0,0,.12)25%,rgba(0,0,0,.26)25%,rgba(0,0,0,.26)37.5%,rgba(0,0,0,.12)37.5%,rgba(0,0,0,.12)100%);transform:scale(1)}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-thumb{transform:scale(.5625);background:radial-gradient(circle closest-side,transparent 0%,transparent 77.78%,rgba(0,0,0,.26)77.78%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-lower{background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-fill-upper{margin-left:9px}.mdl-slider.is-upgraded:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled::-webkit-slider-thumb{-webkit-transform:scale(.667);transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded:disabled::-moz-range-thumb{transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-lower{background-color:rgba(0,0,0,.26);left:-6px}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-webkit-slider-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;-webkit-transform:scale(.667);transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-moz-range-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded:disabled:active::-ms-thumb,.mdl-slider.is-upgraded:disabled::-ms-thumb{transform:scale(.25);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-ms-thumb{transform:scale(.25);background:radial-gradient(circle closest-side,transparent 0%,transparent 50%,rgba(0,0,0,.26)50%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded:disabled::-ms-fill-lower{margin-right:6px;background:linear-gradient(to right,transparent,transparent 25px,rgba(0,0,0,.26)25px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded:disabled::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-fill-upper{margin-left:6px}.mdl-slider__ie-container{height:18px;overflow:visible;border:none;margin:none;padding:none}.mdl-slider__container{height:18px;position:relative;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.mdl-slider__container,.mdl-slider__background-flex{background:0 0;display:-webkit-flex;display:-ms-flexbox;display:flex}.mdl-slider__background-flex{position:absolute;height:2px;width:calc(100% - 52px);top:50%;left:0;margin:0 26px;overflow:hidden;border:0;padding:0;-webkit-transform:translate(0,-1px);transform:translate(0,-1px)}.mdl-slider__background-lower{background:rgb(244,67,54)}.mdl-slider__background-lower,.mdl-slider__background-upper{-webkit-flex:0;-ms-flex:0;flex:0;position:relative;border:0;padding:0}.mdl-slider__background-upper{background:rgba(0,0,0,.26);transition:left .18s cubic-bezier(.4,0,.2,1)}.mdl-snackbar{position:fixed;bottom:0;left:50%;cursor:default;background-color:#323232;z-index:3;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:"Roboto","Helvetica","Arial",sans-serif;will-change:transform;-webkit-transform:translate(0,80px);transform:translate(0,80px);transition:transform .25s cubic-bezier(.4,0,1,1);transition:transform .25s cubic-bezier(.4,0,1,1),-webkit-transform .25s cubic-bezier(.4,0,1,1);pointer-events:none}@media (max-width:479px){.mdl-snackbar{width:100%;left:0;min-height:48px;max-height:80px}}@media (min-width:480px){.mdl-snackbar{min-width:288px;max-width:568px;border-radius:2px;-webkit-transform:translate(-50%,80px);transform:translate(-50%,80px)}}.mdl-snackbar--active{-webkit-transform:translate(0,0);transform:translate(0,0);pointer-events:auto;transition:transform .25s cubic-bezier(0,0,.2,1);transition:transform .25s cubic-bezier(0,0,.2,1),-webkit-transform .25s cubic-bezier(0,0,.2,1)}@media (min-width:480px){.mdl-snackbar--active{-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}}.mdl-snackbar__text{padding:14px 12px 14px 24px;vertical-align:middle;color:#fff;float:left}.mdl-snackbar__action{background:0 0;border:none;color:rgb(100,255,218);float:right;padding:14px 24px 14px 12px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;line-height:1;letter-spacing:0;overflow:hidden;outline:none;opacity:0;pointer-events:none;cursor:pointer;text-decoration:none;text-align:center;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-snackbar__action::-moz-focus-inner{border:0}.mdl-snackbar__action:not([aria-hidden]){opacity:1;pointer-events:auto}.mdl-spinner{display:inline-block;position:relative;width:28px;height:28px}.mdl-spinner:not(.is-upgraded).is-active:after{content:"Loading..."}.mdl-spinner.is-upgraded.is-active{-webkit-animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite;animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite}@-webkit-keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.mdl-spinner__layer{position:absolute;width:100%;height:100%;opacity:0}.mdl-spinner__layer-1{border-color:#42a5f5}.mdl-spinner--single-color .mdl-spinner__layer-1{border-color:rgb(244,67,54)}.mdl-spinner.is-active .mdl-spinner__layer-1{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-2{border-color:#f44336}.mdl-spinner--single-color .mdl-spinner__layer-2{border-color:rgb(244,67,54)}.mdl-spinner.is-active .mdl-spinner__layer-2{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-3{border-color:#fdd835}.mdl-spinner--single-color .mdl-spinner__layer-3{border-color:rgb(244,67,54)}.mdl-spinner.is-active .mdl-spinner__layer-3{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-4{border-color:#4caf50}.mdl-spinner--single-color .mdl-spinner__layer-4{border-color:rgb(244,67,54)}.mdl-spinner.is-active .mdl-spinner__layer-4{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@-webkit-keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}@keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}.mdl-spinner__gap-patch{position:absolute;box-sizing:border-box;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__gap-patch .mdl-spinner__circle{width:1000%;left:-450%}.mdl-spinner__circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__circle-clipper .mdl-spinner__circle{width:200%}.mdl-spinner__circle{box-sizing:border-box;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent!important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0;left:0}.mdl-spinner__left .mdl-spinner__circle{border-right-color:transparent!important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.mdl-spinner.is-active .mdl-spinner__left .mdl-spinner__circle{-webkit-animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__right .mdl-spinner__circle{left:-100%;border-left-color:transparent!important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.mdl-spinner.is-active .mdl-spinner__right .mdl-spinner__circle{-webkit-animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}@keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}.mdl-switch{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0;overflow:visible;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-switch.is-upgraded{padding-left:28px}.mdl-switch__input{line-height:24px}.mdl-switch.is-upgraded .mdl-switch__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-switch__track{background:rgba(0,0,0,.26);position:absolute;left:0;top:5px;height:14px;width:36px;border-radius:14px;cursor:pointer}.mdl-switch.is-checked .mdl-switch__track{background:rgba(244,67,54,.5)}.mdl-switch__track fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__track{background:rgba(0,0,0,.12);cursor:auto}.mdl-switch__thumb{background:#fafafa;position:absolute;left:0;top:2px;height:20px;width:20px;border-radius:50%;cursor:pointer;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:left}.mdl-switch.is-checked .mdl-switch__thumb{background:rgb(244,67,54);left:16px;box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-switch__thumb fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__thumb{background:#bdbdbd;cursor:auto}.mdl-switch__focus-helper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-4px,-4px);transform:translate(-4px,-4px);display:inline-block;box-sizing:border-box;width:8px;height:8px;border-radius:50%;background-color:transparent}.mdl-switch.is-focused .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-switch.is-focused.is-checked .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(244,67,54,.26);background-color:rgba(244,67,54,.26)}.mdl-switch__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0;left:24px}.mdl-switch__label fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__label{color:#bdbdbd;cursor:auto}.mdl-switch__ripple-container{position:absolute;z-index:2;top:-12px;left:-14px;box-sizing:border-box;width:48px;height:48px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000);transition-duration:.4s;transition-timing-function:step-end;transition-property:left}.mdl-switch__ripple-container .mdl-ripple{background:rgb(244,67,54)}.mdl-switch__ripple-container fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__ripple-container{cursor:auto}fieldset[disabled] .mdl-switch .mdl-switch__ripple-container .mdl-ripple,.mdl-switch.is-disabled .mdl-switch__ripple-container .mdl-ripple{background:0 0}.mdl-switch.is-checked .mdl-switch__ripple-container{left:2px}.mdl-tabs{display:block;width:100%}.mdl-tabs__tab-bar{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:space-between;-ms-flex-line-pack:justify;align-content:space-between;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;height:48px;padding:0;margin:0;border-bottom:1px solid #e0e0e0}.mdl-tabs__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(0,0,0,.54);overflow:hidden}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active{color:rgba(0,0,0,.87)}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active:after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(244,67,54);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-tabs__tab .mdl-tabs__ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-tabs__tab .mdl-tabs__ripple-container .mdl-ripple{background:rgb(244,67,54)}.mdl-tabs__panel{display:block}.mdl-tabs.is-upgraded .mdl-tabs__panel{display:none}.mdl-tabs.is-upgraded .mdl-tabs__panel.is-active{display:block}@-webkit-keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}@keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}.mdl-textfield{position:relative;font-size:16px;display:inline-block;box-sizing:border-box;width:300px;max-width:100%;margin:0;padding:20px 0}.mdl-textfield .mdl-button{position:absolute;bottom:20px}.mdl-textfield--align-right{text-align:right}.mdl-textfield--full-width{width:100%}.mdl-textfield--expandable{min-width:32px;width:auto;min-height:32px}.mdl-textfield--expandable .mdl-button--icon{top:16px}.mdl-textfield__input{border:none;border-bottom:1px solid rgba(0,0,0,.12);display:block;font-size:16px;font-family:"Helvetica","Arial",sans-serif;margin:0;padding:4px 0;width:100%;background:0 0;text-align:left;color:inherit}.mdl-textfield__input[type="number"]{-moz-appearance:textfield}.mdl-textfield__input[type="number"]::-webkit-inner-spin-button,.mdl-textfield__input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.mdl-textfield.is-focused .mdl-textfield__input{outline:none}.mdl-textfield.is-invalid .mdl-textfield__input{border-color:#d50000;box-shadow:none}fieldset[disabled] .mdl-textfield .mdl-textfield__input,.mdl-textfield.is-disabled .mdl-textfield__input{background-color:transparent;border-bottom:1px dotted rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-textfield textarea.mdl-textfield__input{display:block}.mdl-textfield__label{bottom:0;color:rgba(0,0,0,.26);font-size:16px;left:0;right:0;pointer-events:none;position:absolute;display:block;top:24px;width:100%;overflow:hidden;white-space:nowrap;text-align:left}.mdl-textfield.is-dirty .mdl-textfield__label,.mdl-textfield.has-placeholder .mdl-textfield__label{visibility:hidden}.mdl-textfield--floating-label .mdl-textfield__label{transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{transition:none}fieldset[disabled] .mdl-textfield .mdl-textfield__label,.mdl-textfield.is-disabled.is-disabled .mdl-textfield__label{color:rgba(0,0,0,.26)}.mdl-textfield--floating-label.is-focused .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{color:rgb(244,67,54);font-size:12px;top:4px;visibility:visible}.mdl-textfield--floating-label.is-focused .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__expandable-holder .mdl-textfield__label{top:-16px}.mdl-textfield--floating-label.is-invalid .mdl-textfield__label{color:#d50000;font-size:12px}.mdl-textfield__label:after{background-color:rgb(244,67,54);bottom:20px;content:'';height:2px;left:45%;position:absolute;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);visibility:hidden;width:10px}.mdl-textfield.is-focused .mdl-textfield__label:after{left:0;visibility:visible;width:100%}.mdl-textfield.is-invalid .mdl-textfield__label:after{background-color:#d50000}.mdl-textfield__error{color:#d50000;position:absolute;font-size:12px;margin-top:3px;visibility:hidden;display:block}.mdl-textfield.is-invalid .mdl-textfield__error{visibility:visible}.mdl-textfield__expandable-holder{display:inline-block;position:relative;margin-left:32px;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-block;max-width:.1px}.mdl-textfield.is-focused .mdl-textfield__expandable-holder,.mdl-textfield.is-dirty .mdl-textfield__expandable-holder{max-width:600px}.mdl-textfield__expandable-holder .mdl-textfield__label:after{bottom:0}.mdl-tooltip{-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:top center;transform-origin:top center;z-index:999;background:rgba(97,97,97,.9);border-radius:2px;color:#fff;display:inline-block;font-size:10px;font-weight:500;line-height:14px;max-width:170px;position:fixed;top:-500px;left:-500px;padding:8px;text-align:center}.mdl-tooltip.is-active{-webkit-animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards;animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards}.mdl-tooltip--large{line-height:14px;font-size:14px;padding:16px}@-webkit-keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}@keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}.mdl-shadow--2dp{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-shadow--3dp{box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-shadow--4dp{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2)}.mdl-shadow--6dp{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.2)}.mdl-shadow--8dp{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.2)}.mdl-shadow--16dp{box-shadow:0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)}.mdl-shadow--24dp{box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2)}.mdl-grid{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;margin:0 auto;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.mdl-grid.mdl-grid--no-spacing{padding:0}.mdl-cell{box-sizing:border-box}.mdl-cell--top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.mdl-cell--middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-cell--bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.mdl-cell--stretch{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.mdl-grid.mdl-grid--no-spacing>.mdl-cell{margin:0}.mdl-cell--order-1{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12{-webkit-order:12;-ms-flex-order:12;order:12}@media (max-width:479px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:100%}.mdl-cell--hide-phone{display:none!important}.mdl-cell--order-1-phone.mdl-cell--order-1-phone{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-phone.mdl-cell--order-2-phone{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-phone.mdl-cell--order-3-phone{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-phone.mdl-cell--order-4-phone{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-phone.mdl-cell--order-5-phone{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-phone.mdl-cell--order-6-phone{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-phone.mdl-cell--order-7-phone{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-phone.mdl-cell--order-8-phone{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-phone.mdl-cell--order-9-phone{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-phone.mdl-cell--order-10-phone{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-phone.mdl-cell--order-11-phone{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-phone.mdl-cell--order-12-phone{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:25%}.mdl-cell--2-col,.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:50%}.mdl-cell--3-col,.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:75%}.mdl-cell--4-col,.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:100%}.mdl-cell--5-col,.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:100%}.mdl-cell--6-col,.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:100%}.mdl-cell--7-col,.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:100%}.mdl-cell--8-col,.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:25%}.mdl-cell--2-offset,.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:50%}.mdl-cell--3-offset,.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:75%}}@media (min-width:480px) and (max-width:839px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:50%}.mdl-cell--hide-tablet{display:none!important}.mdl-cell--order-1-tablet.mdl-cell--order-1-tablet{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-tablet.mdl-cell--order-2-tablet{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-tablet.mdl-cell--order-3-tablet{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-tablet.mdl-cell--order-4-tablet{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-tablet.mdl-cell--order-5-tablet{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-tablet.mdl-cell--order-6-tablet{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-tablet.mdl-cell--order-7-tablet{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-tablet.mdl-cell--order-8-tablet{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-tablet.mdl-cell--order-9-tablet{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-tablet.mdl-cell--order-10-tablet{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-tablet.mdl-cell--order-11-tablet{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-tablet.mdl-cell--order-12-tablet{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:calc(12.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:12.5%}.mdl-cell--2-col,.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:25%}.mdl-cell--3-col,.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:calc(37.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:37.5%}.mdl-cell--4-col,.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:50%}.mdl-cell--5-col,.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:calc(62.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:62.5%}.mdl-cell--6-col,.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:75%}.mdl-cell--7-col,.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:calc(87.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:87.5%}.mdl-cell--8-col,.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:calc(12.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:12.5%}.mdl-cell--2-offset,.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:25%}.mdl-cell--3-offset,.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:calc(37.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:37.5%}.mdl-cell--4-offset,.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:50%}.mdl-cell--5-offset,.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:calc(62.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:62.5%}.mdl-cell--6-offset,.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:75%}.mdl-cell--7-offset,.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:calc(87.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:87.5%}}@media (min-width:840px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:33.3333333333%}.mdl-cell--hide-desktop{display:none!important}.mdl-cell--order-1-desktop.mdl-cell--order-1-desktop{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-desktop.mdl-cell--order-2-desktop{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-desktop.mdl-cell--order-3-desktop{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-desktop.mdl-cell--order-4-desktop{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-desktop.mdl-cell--order-5-desktop{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-desktop.mdl-cell--order-6-desktop{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-desktop.mdl-cell--order-7-desktop{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-desktop.mdl-cell--order-8-desktop{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-desktop.mdl-cell--order-9-desktop{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-desktop.mdl-cell--order-10-desktop{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-desktop.mdl-cell--order-11-desktop{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-desktop.mdl-cell--order-12-desktop{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:calc(8.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:8.3333333333%}.mdl-cell--2-col,.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:calc(16.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:16.6666666667%}.mdl-cell--3-col,.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:25%}.mdl-cell--4-col,.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:33.3333333333%}.mdl-cell--5-col,.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:calc(41.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:41.6666666667%}.mdl-cell--6-col,.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:50%}.mdl-cell--7-col,.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:calc(58.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:58.3333333333%}.mdl-cell--8-col,.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:calc(66.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:66.6666666667%}.mdl-cell--9-col,.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:75%}.mdl-cell--10-col,.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:calc(83.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:83.3333333333%}.mdl-cell--11-col,.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:calc(91.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:91.6666666667%}.mdl-cell--12-col,.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:calc(8.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:8.3333333333%}.mdl-cell--2-offset,.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:calc(16.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:16.6666666667%}.mdl-cell--3-offset,.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:25%}.mdl-cell--4-offset,.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:calc(33.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:33.3333333333%}.mdl-cell--5-offset,.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:calc(41.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:41.6666666667%}.mdl-cell--6-offset,.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:50%}.mdl-cell--7-offset,.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:calc(58.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:58.3333333333%}.mdl-cell--8-offset,.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:calc(66.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:66.6666666667%}.mdl-cell--9-offset,.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:75%}.mdl-cell--10-offset,.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:calc(83.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:83.3333333333%}.mdl-cell--11-offset,.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:calc(91.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:91.6666666667%}}body{margin:0}.styleguide-demo h1{margin:48px 24px 0}.styleguide-demo h1:after{content:'';display:block;width:100%;border-bottom:1px solid rgba(0,0,0,.5);margin-top:24px}.styleguide-demo{opacity:0;transition:opacity .6s ease}.styleguide-masthead{height:256px;background:#212121;padding:115px 16px 0}.styleguide-container{position:relative;max-width:960px;width:100%}.styleguide-title{color:#fff;bottom:auto;position:relative;font-size:56px;font-weight:300;line-height:1;letter-spacing:-.02em}.styleguide-title:after{border-bottom:0}.styleguide-title span{font-weight:300}.mdl-styleguide .mdl-layout__drawer .mdl-navigation__link{padding:10px 24px}.demosLoaded .styleguide-demo{opacity:1}iframe{display:block;width:100%;border:none}iframe.heightSet{overflow:hidden}.demo-wrapper{margin:24px}.demo-wrapper iframe{border:1px solid rgba(0,0,0,.5)} \ No newline at end of file diff --git a/modules/material/www/material.teal-blue.1.2.1.min.css b/modules/material/www/material.teal-blue.1.2.1.min.css new file mode 100644 index 00000000..e0743668 --- /dev/null +++ b/modules/material/www/material.teal-blue.1.2.1.min.css @@ -0,0 +1,8 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.2.1 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +@charset "UTF-8";html{color:rgba(0,0,0,.87)}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.browserupgrade{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.hidden{display:none!important}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}@media print{*,*:before,*:after,*:first-letter{background:transparent!important;color:#000!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}a,.mdl-accordion,.mdl-button,.mdl-card,.mdl-checkbox,.mdl-dropdown-menu,.mdl-icon-toggle,.mdl-item,.mdl-radio,.mdl-slider,.mdl-switch,.mdl-tabs__tab{-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:rgba(255,255,255,0)}html{width:100%;height:100%;-ms-touch-action:manipulation;touch-action:manipulation}body{width:100%;min-height:100%}main{display:block}*[hidden]{display:none!important}html,body{font-family:"Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:20px}h1,h2,h3,h4,h5,h6,p{padding:0}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400;line-height:1.35;letter-spacing:-.02em;opacity:.54;font-size:.6em}h1{font-size:56px;line-height:1.35;letter-spacing:-.02em;margin:24px 0}h1,h2{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h2{font-size:45px;line-height:48px}h2,h3{margin:24px 0}h3{font-size:34px;line-height:40px}h3,h4{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h4{font-size:24px;line-height:32px;-moz-osx-font-smoothing:grayscale;margin:24px 0 16px}h5{font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;margin:24px 0 16px}h6{font-size:16px;letter-spacing:.04em}h6,p{font-weight:400;line-height:24px}p{font-size:14px;letter-spacing:0;margin:0 0 16px}a{color:rgb(68,138,255);font-weight:500}blockquote{font-family:"Roboto","Helvetica","Arial",sans-serif;position:relative;font-size:24px;font-weight:300;font-style:italic;line-height:1.35;letter-spacing:.08em}blockquote:before{position:absolute;left:-.5em;content:'“'}blockquote:after{content:'”';margin-left:-.05em}mark{background-color:#f4ff81}dt{font-weight:700}address{font-size:12px;line-height:1;font-style:normal}address,ul,ol{font-weight:400;letter-spacing:0}ul,ol{font-size:14px;line-height:24px}.mdl-typography--display-4,.mdl-typography--display-4-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:112px;font-weight:300;line-height:1;letter-spacing:-.04em}.mdl-typography--display-4-color-contrast{opacity:.54}.mdl-typography--display-3,.mdl-typography--display-3-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:56px;font-weight:400;line-height:1.35;letter-spacing:-.02em}.mdl-typography--display-3-color-contrast{opacity:.54}.mdl-typography--display-2,.mdl-typography--display-2-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:45px;font-weight:400;line-height:48px}.mdl-typography--display-2-color-contrast{opacity:.54}.mdl-typography--display-1,.mdl-typography--display-1-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:34px;font-weight:400;line-height:40px}.mdl-typography--display-1-color-contrast{opacity:.54}.mdl-typography--headline,.mdl-typography--headline-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:24px;font-weight:400;line-height:32px;-moz-osx-font-smoothing:grayscale}.mdl-typography--headline-color-contrast{opacity:.87}.mdl-typography--title,.mdl-typography--title-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}.mdl-typography--title-color-contrast{opacity:.87}.mdl-typography--subhead,.mdl-typography--subhead-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;line-height:24px;letter-spacing:.04em}.mdl-typography--subhead-color-contrast{opacity:.87}.mdl-typography--body-2,.mdl-typography--body-2-color-contrast{font-size:14px;font-weight:700;line-height:24px;letter-spacing:0}.mdl-typography--body-2-color-contrast{opacity:.87}.mdl-typography--body-1,.mdl-typography--body-1-color-contrast{font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-color-contrast{opacity:.87}.mdl-typography--body-2-force-preferred-font,.mdl-typography--body-2-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:24px;letter-spacing:0}.mdl-typography--body-2-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--body-1-force-preferred-font,.mdl-typography--body-1-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--caption,.mdl-typography--caption-force-preferred-font{font-size:12px;font-weight:400;line-height:1;letter-spacing:0}.mdl-typography--caption-force-preferred-font{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--caption-color-contrast,.mdl-typography--caption-force-preferred-font-color-contrast{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;opacity:.54}.mdl-typography--caption-force-preferred-font-color-contrast,.mdl-typography--menu{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--menu{font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--menu-color-contrast{opacity:.87}.mdl-typography--menu-color-contrast,.mdl-typography--button,.mdl-typography--button-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--button,.mdl-typography--button-color-contrast{text-transform:uppercase}.mdl-typography--button-color-contrast{opacity:.87}.mdl-typography--text-left{text-align:left}.mdl-typography--text-right{text-align:right}.mdl-typography--text-center{text-align:center}.mdl-typography--text-justify{text-align:justify}.mdl-typography--text-nowrap{white-space:nowrap}.mdl-typography--text-lowercase{text-transform:lowercase}.mdl-typography--text-uppercase{text-transform:uppercase}.mdl-typography--text-capitalize{text-transform:capitalize}.mdl-typography--font-thin{font-weight:200!important}.mdl-typography--font-light{font-weight:300!important}.mdl-typography--font-regular{font-weight:400!important}.mdl-typography--font-medium{font-weight:500!important}.mdl-typography--font-bold{font-weight:700!important}.mdl-typography--font-black{font-weight:900!important}.material-icons{font-family:'Material Icons';font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased}.mdl-color-text--red{color:#f44336 !important}.mdl-color--red{background-color:#f44336 !important}.mdl-color-text--red-50{color:#ffebee !important}.mdl-color--red-50{background-color:#ffebee !important}.mdl-color-text--red-100{color:#ffcdd2 !important}.mdl-color--red-100{background-color:#ffcdd2 !important}.mdl-color-text--red-200{color:#ef9a9a !important}.mdl-color--red-200{background-color:#ef9a9a !important}.mdl-color-text--red-300{color:#e57373 !important}.mdl-color--red-300{background-color:#e57373 !important}.mdl-color-text--red-400{color:#ef5350 !important}.mdl-color--red-400{background-color:#ef5350 !important}.mdl-color-text--red-500{color:#f44336 !important}.mdl-color--red-500{background-color:#f44336 !important}.mdl-color-text--red-600{color:#e53935 !important}.mdl-color--red-600{background-color:#e53935 !important}.mdl-color-text--red-700{color:#d32f2f !important}.mdl-color--red-700{background-color:#d32f2f !important}.mdl-color-text--red-800{color:#c62828 !important}.mdl-color--red-800{background-color:#c62828 !important}.mdl-color-text--red-900{color:#b71c1c !important}.mdl-color--red-900{background-color:#b71c1c !important}.mdl-color-text--red-A100{color:#ff8a80 !important}.mdl-color--red-A100{background-color:#ff8a80 !important}.mdl-color-text--red-A200{color:#ff5252 !important}.mdl-color--red-A200{background-color:#ff5252 !important}.mdl-color-text--red-A400{color:#ff1744 !important}.mdl-color--red-A400{background-color:#ff1744 !important}.mdl-color-text--red-A700{color:#d50000 !important}.mdl-color--red-A700{background-color:#d50000 !important}.mdl-color-text--pink{color:#e91e63 !important}.mdl-color--pink{background-color:#e91e63 !important}.mdl-color-text--pink-50{color:#fce4ec !important}.mdl-color--pink-50{background-color:#fce4ec !important}.mdl-color-text--pink-100{color:#f8bbd0 !important}.mdl-color--pink-100{background-color:#f8bbd0 !important}.mdl-color-text--pink-200{color:#f48fb1 !important}.mdl-color--pink-200{background-color:#f48fb1 !important}.mdl-color-text--pink-300{color:#f06292 !important}.mdl-color--pink-300{background-color:#f06292 !important}.mdl-color-text--pink-400{color:#ec407a !important}.mdl-color--pink-400{background-color:#ec407a !important}.mdl-color-text--pink-500{color:#e91e63 !important}.mdl-color--pink-500{background-color:#e91e63 !important}.mdl-color-text--pink-600{color:#d81b60 !important}.mdl-color--pink-600{background-color:#d81b60 !important}.mdl-color-text--pink-700{color:#c2185b !important}.mdl-color--pink-700{background-color:#c2185b !important}.mdl-color-text--pink-800{color:#ad1457 !important}.mdl-color--pink-800{background-color:#ad1457 !important}.mdl-color-text--pink-900{color:#880e4f !important}.mdl-color--pink-900{background-color:#880e4f !important}.mdl-color-text--pink-A100{color:#ff80ab !important}.mdl-color--pink-A100{background-color:#ff80ab !important}.mdl-color-text--pink-A200{color:#ff4081 !important}.mdl-color--pink-A200{background-color:#ff4081 !important}.mdl-color-text--pink-A400{color:#f50057 !important}.mdl-color--pink-A400{background-color:#f50057 !important}.mdl-color-text--pink-A700{color:#c51162 !important}.mdl-color--pink-A700{background-color:#c51162 !important}.mdl-color-text--purple{color:#9c27b0 !important}.mdl-color--purple{background-color:#9c27b0 !important}.mdl-color-text--purple-50{color:#f3e5f5 !important}.mdl-color--purple-50{background-color:#f3e5f5 !important}.mdl-color-text--purple-100{color:#e1bee7 !important}.mdl-color--purple-100{background-color:#e1bee7 !important}.mdl-color-text--purple-200{color:#ce93d8 !important}.mdl-color--purple-200{background-color:#ce93d8 !important}.mdl-color-text--purple-300{color:#ba68c8 !important}.mdl-color--purple-300{background-color:#ba68c8 !important}.mdl-color-text--purple-400{color:#ab47bc !important}.mdl-color--purple-400{background-color:#ab47bc !important}.mdl-color-text--purple-500{color:#9c27b0 !important}.mdl-color--purple-500{background-color:#9c27b0 !important}.mdl-color-text--purple-600{color:#8e24aa !important}.mdl-color--purple-600{background-color:#8e24aa !important}.mdl-color-text--purple-700{color:#7b1fa2 !important}.mdl-color--purple-700{background-color:#7b1fa2 !important}.mdl-color-text--purple-800{color:#6a1b9a !important}.mdl-color--purple-800{background-color:#6a1b9a !important}.mdl-color-text--purple-900{color:#4a148c !important}.mdl-color--purple-900{background-color:#4a148c !important}.mdl-color-text--purple-A100{color:#ea80fc !important}.mdl-color--purple-A100{background-color:#ea80fc !important}.mdl-color-text--purple-A200{color:#e040fb !important}.mdl-color--purple-A200{background-color:#e040fb !important}.mdl-color-text--purple-A400{color:#d500f9 !important}.mdl-color--purple-A400{background-color:#d500f9 !important}.mdl-color-text--purple-A700{color:#a0f !important}.mdl-color--purple-A700{background-color:#a0f !important}.mdl-color-text--deep-purple{color:#673ab7 !important}.mdl-color--deep-purple{background-color:#673ab7 !important}.mdl-color-text--deep-purple-50{color:#ede7f6 !important}.mdl-color--deep-purple-50{background-color:#ede7f6 !important}.mdl-color-text--deep-purple-100{color:#d1c4e9 !important}.mdl-color--deep-purple-100{background-color:#d1c4e9 !important}.mdl-color-text--deep-purple-200{color:#b39ddb !important}.mdl-color--deep-purple-200{background-color:#b39ddb !important}.mdl-color-text--deep-purple-300{color:#9575cd !important}.mdl-color--deep-purple-300{background-color:#9575cd !important}.mdl-color-text--deep-purple-400{color:#7e57c2 !important}.mdl-color--deep-purple-400{background-color:#7e57c2 !important}.mdl-color-text--deep-purple-500{color:#673ab7 !important}.mdl-color--deep-purple-500{background-color:#673ab7 !important}.mdl-color-text--deep-purple-600{color:#5e35b1 !important}.mdl-color--deep-purple-600{background-color:#5e35b1 !important}.mdl-color-text--deep-purple-700{color:#512da8 !important}.mdl-color--deep-purple-700{background-color:#512da8 !important}.mdl-color-text--deep-purple-800{color:#4527a0 !important}.mdl-color--deep-purple-800{background-color:#4527a0 !important}.mdl-color-text--deep-purple-900{color:#311b92 !important}.mdl-color--deep-purple-900{background-color:#311b92 !important}.mdl-color-text--deep-purple-A100{color:#b388ff !important}.mdl-color--deep-purple-A100{background-color:#b388ff !important}.mdl-color-text--deep-purple-A200{color:#7c4dff !important}.mdl-color--deep-purple-A200{background-color:#7c4dff !important}.mdl-color-text--deep-purple-A400{color:#651fff !important}.mdl-color--deep-purple-A400{background-color:#651fff !important}.mdl-color-text--deep-purple-A700{color:#6200ea !important}.mdl-color--deep-purple-A700{background-color:#6200ea !important}.mdl-color-text--indigo{color:#3f51b5 !important}.mdl-color--indigo{background-color:#3f51b5 !important}.mdl-color-text--indigo-50{color:#e8eaf6 !important}.mdl-color--indigo-50{background-color:#e8eaf6 !important}.mdl-color-text--indigo-100{color:#c5cae9 !important}.mdl-color--indigo-100{background-color:#c5cae9 !important}.mdl-color-text--indigo-200{color:#9fa8da !important}.mdl-color--indigo-200{background-color:#9fa8da !important}.mdl-color-text--indigo-300{color:#7986cb !important}.mdl-color--indigo-300{background-color:#7986cb !important}.mdl-color-text--indigo-400{color:#5c6bc0 !important}.mdl-color--indigo-400{background-color:#5c6bc0 !important}.mdl-color-text--indigo-500{color:#3f51b5 !important}.mdl-color--indigo-500{background-color:#3f51b5 !important}.mdl-color-text--indigo-600{color:#3949ab !important}.mdl-color--indigo-600{background-color:#3949ab !important}.mdl-color-text--indigo-700{color:#303f9f !important}.mdl-color--indigo-700{background-color:#303f9f !important}.mdl-color-text--indigo-800{color:#283593 !important}.mdl-color--indigo-800{background-color:#283593 !important}.mdl-color-text--indigo-900{color:#1a237e !important}.mdl-color--indigo-900{background-color:#1a237e !important}.mdl-color-text--indigo-A100{color:#8c9eff !important}.mdl-color--indigo-A100{background-color:#8c9eff !important}.mdl-color-text--indigo-A200{color:#536dfe !important}.mdl-color--indigo-A200{background-color:#536dfe !important}.mdl-color-text--indigo-A400{color:#3d5afe !important}.mdl-color--indigo-A400{background-color:#3d5afe !important}.mdl-color-text--indigo-A700{color:#304ffe !important}.mdl-color--indigo-A700{background-color:#304ffe !important}.mdl-color-text--blue{color:#2196f3 !important}.mdl-color--blue{background-color:#2196f3 !important}.mdl-color-text--blue-50{color:#e3f2fd !important}.mdl-color--blue-50{background-color:#e3f2fd !important}.mdl-color-text--blue-100{color:#bbdefb !important}.mdl-color--blue-100{background-color:#bbdefb !important}.mdl-color-text--blue-200{color:#90caf9 !important}.mdl-color--blue-200{background-color:#90caf9 !important}.mdl-color-text--blue-300{color:#64b5f6 !important}.mdl-color--blue-300{background-color:#64b5f6 !important}.mdl-color-text--blue-400{color:#42a5f5 !important}.mdl-color--blue-400{background-color:#42a5f5 !important}.mdl-color-text--blue-500{color:#2196f3 !important}.mdl-color--blue-500{background-color:#2196f3 !important}.mdl-color-text--blue-600{color:#1e88e5 !important}.mdl-color--blue-600{background-color:#1e88e5 !important}.mdl-color-text--blue-700{color:#1976d2 !important}.mdl-color--blue-700{background-color:#1976d2 !important}.mdl-color-text--blue-800{color:#1565c0 !important}.mdl-color--blue-800{background-color:#1565c0 !important}.mdl-color-text--blue-900{color:#0d47a1 !important}.mdl-color--blue-900{background-color:#0d47a1 !important}.mdl-color-text--blue-A100{color:#82b1ff !important}.mdl-color--blue-A100{background-color:#82b1ff !important}.mdl-color-text--blue-A200{color:#448aff !important}.mdl-color--blue-A200{background-color:#448aff !important}.mdl-color-text--blue-A400{color:#2979ff !important}.mdl-color--blue-A400{background-color:#2979ff !important}.mdl-color-text--blue-A700{color:#2962ff !important}.mdl-color--blue-A700{background-color:#2962ff !important}.mdl-color-text--light-blue{color:#03a9f4 !important}.mdl-color--light-blue{background-color:#03a9f4 !important}.mdl-color-text--light-blue-50{color:#e1f5fe !important}.mdl-color--light-blue-50{background-color:#e1f5fe !important}.mdl-color-text--light-blue-100{color:#b3e5fc !important}.mdl-color--light-blue-100{background-color:#b3e5fc !important}.mdl-color-text--light-blue-200{color:#81d4fa !important}.mdl-color--light-blue-200{background-color:#81d4fa !important}.mdl-color-text--light-blue-300{color:#4fc3f7 !important}.mdl-color--light-blue-300{background-color:#4fc3f7 !important}.mdl-color-text--light-blue-400{color:#29b6f6 !important}.mdl-color--light-blue-400{background-color:#29b6f6 !important}.mdl-color-text--light-blue-500{color:#03a9f4 !important}.mdl-color--light-blue-500{background-color:#03a9f4 !important}.mdl-color-text--light-blue-600{color:#039be5 !important}.mdl-color--light-blue-600{background-color:#039be5 !important}.mdl-color-text--light-blue-700{color:#0288d1 !important}.mdl-color--light-blue-700{background-color:#0288d1 !important}.mdl-color-text--light-blue-800{color:#0277bd !important}.mdl-color--light-blue-800{background-color:#0277bd !important}.mdl-color-text--light-blue-900{color:#01579b !important}.mdl-color--light-blue-900{background-color:#01579b !important}.mdl-color-text--light-blue-A100{color:#80d8ff !important}.mdl-color--light-blue-A100{background-color:#80d8ff !important}.mdl-color-text--light-blue-A200{color:#40c4ff !important}.mdl-color--light-blue-A200{background-color:#40c4ff !important}.mdl-color-text--light-blue-A400{color:#00b0ff !important}.mdl-color--light-blue-A400{background-color:#00b0ff !important}.mdl-color-text--light-blue-A700{color:#0091ea !important}.mdl-color--light-blue-A700{background-color:#0091ea !important}.mdl-color-text--cyan{color:#00bcd4 !important}.mdl-color--cyan{background-color:#00bcd4 !important}.mdl-color-text--cyan-50{color:#e0f7fa !important}.mdl-color--cyan-50{background-color:#e0f7fa !important}.mdl-color-text--cyan-100{color:#b2ebf2 !important}.mdl-color--cyan-100{background-color:#b2ebf2 !important}.mdl-color-text--cyan-200{color:#80deea !important}.mdl-color--cyan-200{background-color:#80deea !important}.mdl-color-text--cyan-300{color:#4dd0e1 !important}.mdl-color--cyan-300{background-color:#4dd0e1 !important}.mdl-color-text--cyan-400{color:#26c6da !important}.mdl-color--cyan-400{background-color:#26c6da !important}.mdl-color-text--cyan-500{color:#00bcd4 !important}.mdl-color--cyan-500{background-color:#00bcd4 !important}.mdl-color-text--cyan-600{color:#00acc1 !important}.mdl-color--cyan-600{background-color:#00acc1 !important}.mdl-color-text--cyan-700{color:#0097a7 !important}.mdl-color--cyan-700{background-color:#0097a7 !important}.mdl-color-text--cyan-800{color:#00838f !important}.mdl-color--cyan-800{background-color:#00838f !important}.mdl-color-text--cyan-900{color:#006064 !important}.mdl-color--cyan-900{background-color:#006064 !important}.mdl-color-text--cyan-A100{color:#84ffff !important}.mdl-color--cyan-A100{background-color:#84ffff !important}.mdl-color-text--cyan-A200{color:#18ffff !important}.mdl-color--cyan-A200{background-color:#18ffff !important}.mdl-color-text--cyan-A400{color:#00e5ff !important}.mdl-color--cyan-A400{background-color:#00e5ff !important}.mdl-color-text--cyan-A700{color:#00b8d4 !important}.mdl-color--cyan-A700{background-color:#00b8d4 !important}.mdl-color-text--teal{color:#009688 !important}.mdl-color--teal{background-color:#009688 !important}.mdl-color-text--teal-50{color:#e0f2f1 !important}.mdl-color--teal-50{background-color:#e0f2f1 !important}.mdl-color-text--teal-100{color:#b2dfdb !important}.mdl-color--teal-100{background-color:#b2dfdb !important}.mdl-color-text--teal-200{color:#80cbc4 !important}.mdl-color--teal-200{background-color:#80cbc4 !important}.mdl-color-text--teal-300{color:#4db6ac !important}.mdl-color--teal-300{background-color:#4db6ac !important}.mdl-color-text--teal-400{color:#26a69a !important}.mdl-color--teal-400{background-color:#26a69a !important}.mdl-color-text--teal-500{color:#009688 !important}.mdl-color--teal-500{background-color:#009688 !important}.mdl-color-text--teal-600{color:#00897b !important}.mdl-color--teal-600{background-color:#00897b !important}.mdl-color-text--teal-700{color:#00796b !important}.mdl-color--teal-700{background-color:#00796b !important}.mdl-color-text--teal-800{color:#00695c !important}.mdl-color--teal-800{background-color:#00695c !important}.mdl-color-text--teal-900{color:#004d40 !important}.mdl-color--teal-900{background-color:#004d40 !important}.mdl-color-text--teal-A100{color:#a7ffeb !important}.mdl-color--teal-A100{background-color:#a7ffeb !important}.mdl-color-text--teal-A200{color:#64ffda !important}.mdl-color--teal-A200{background-color:#64ffda !important}.mdl-color-text--teal-A400{color:#1de9b6 !important}.mdl-color--teal-A400{background-color:#1de9b6 !important}.mdl-color-text--teal-A700{color:#00bfa5 !important}.mdl-color--teal-A700{background-color:#00bfa5 !important}.mdl-color-text--green{color:#4caf50 !important}.mdl-color--green{background-color:#4caf50 !important}.mdl-color-text--green-50{color:#e8f5e9 !important}.mdl-color--green-50{background-color:#e8f5e9 !important}.mdl-color-text--green-100{color:#c8e6c9 !important}.mdl-color--green-100{background-color:#c8e6c9 !important}.mdl-color-text--green-200{color:#a5d6a7 !important}.mdl-color--green-200{background-color:#a5d6a7 !important}.mdl-color-text--green-300{color:#81c784 !important}.mdl-color--green-300{background-color:#81c784 !important}.mdl-color-text--green-400{color:#66bb6a !important}.mdl-color--green-400{background-color:#66bb6a !important}.mdl-color-text--green-500{color:#4caf50 !important}.mdl-color--green-500{background-color:#4caf50 !important}.mdl-color-text--green-600{color:#43a047 !important}.mdl-color--green-600{background-color:#43a047 !important}.mdl-color-text--green-700{color:#388e3c !important}.mdl-color--green-700{background-color:#388e3c !important}.mdl-color-text--green-800{color:#2e7d32 !important}.mdl-color--green-800{background-color:#2e7d32 !important}.mdl-color-text--green-900{color:#1b5e20 !important}.mdl-color--green-900{background-color:#1b5e20 !important}.mdl-color-text--green-A100{color:#b9f6ca !important}.mdl-color--green-A100{background-color:#b9f6ca !important}.mdl-color-text--green-A200{color:#69f0ae !important}.mdl-color--green-A200{background-color:#69f0ae !important}.mdl-color-text--green-A400{color:#00e676 !important}.mdl-color--green-A400{background-color:#00e676 !important}.mdl-color-text--green-A700{color:#00c853 !important}.mdl-color--green-A700{background-color:#00c853 !important}.mdl-color-text--light-green{color:#8bc34a !important}.mdl-color--light-green{background-color:#8bc34a !important}.mdl-color-text--light-green-50{color:#f1f8e9 !important}.mdl-color--light-green-50{background-color:#f1f8e9 !important}.mdl-color-text--light-green-100{color:#dcedc8 !important}.mdl-color--light-green-100{background-color:#dcedc8 !important}.mdl-color-text--light-green-200{color:#c5e1a5 !important}.mdl-color--light-green-200{background-color:#c5e1a5 !important}.mdl-color-text--light-green-300{color:#aed581 !important}.mdl-color--light-green-300{background-color:#aed581 !important}.mdl-color-text--light-green-400{color:#9ccc65 !important}.mdl-color--light-green-400{background-color:#9ccc65 !important}.mdl-color-text--light-green-500{color:#8bc34a !important}.mdl-color--light-green-500{background-color:#8bc34a !important}.mdl-color-text--light-green-600{color:#7cb342 !important}.mdl-color--light-green-600{background-color:#7cb342 !important}.mdl-color-text--light-green-700{color:#689f38 !important}.mdl-color--light-green-700{background-color:#689f38 !important}.mdl-color-text--light-green-800{color:#558b2f !important}.mdl-color--light-green-800{background-color:#558b2f !important}.mdl-color-text--light-green-900{color:#33691e !important}.mdl-color--light-green-900{background-color:#33691e !important}.mdl-color-text--light-green-A100{color:#ccff90 !important}.mdl-color--light-green-A100{background-color:#ccff90 !important}.mdl-color-text--light-green-A200{color:#b2ff59 !important}.mdl-color--light-green-A200{background-color:#b2ff59 !important}.mdl-color-text--light-green-A400{color:#76ff03 !important}.mdl-color--light-green-A400{background-color:#76ff03 !important}.mdl-color-text--light-green-A700{color:#64dd17 !important}.mdl-color--light-green-A700{background-color:#64dd17 !important}.mdl-color-text--lime{color:#cddc39 !important}.mdl-color--lime{background-color:#cddc39 !important}.mdl-color-text--lime-50{color:#f9fbe7 !important}.mdl-color--lime-50{background-color:#f9fbe7 !important}.mdl-color-text--lime-100{color:#f0f4c3 !important}.mdl-color--lime-100{background-color:#f0f4c3 !important}.mdl-color-text--lime-200{color:#e6ee9c !important}.mdl-color--lime-200{background-color:#e6ee9c !important}.mdl-color-text--lime-300{color:#dce775 !important}.mdl-color--lime-300{background-color:#dce775 !important}.mdl-color-text--lime-400{color:#d4e157 !important}.mdl-color--lime-400{background-color:#d4e157 !important}.mdl-color-text--lime-500{color:#cddc39 !important}.mdl-color--lime-500{background-color:#cddc39 !important}.mdl-color-text--lime-600{color:#c0ca33 !important}.mdl-color--lime-600{background-color:#c0ca33 !important}.mdl-color-text--lime-700{color:#afb42b !important}.mdl-color--lime-700{background-color:#afb42b !important}.mdl-color-text--lime-800{color:#9e9d24 !important}.mdl-color--lime-800{background-color:#9e9d24 !important}.mdl-color-text--lime-900{color:#827717 !important}.mdl-color--lime-900{background-color:#827717 !important}.mdl-color-text--lime-A100{color:#f4ff81 !important}.mdl-color--lime-A100{background-color:#f4ff81 !important}.mdl-color-text--lime-A200{color:#eeff41 !important}.mdl-color--lime-A200{background-color:#eeff41 !important}.mdl-color-text--lime-A400{color:#c6ff00 !important}.mdl-color--lime-A400{background-color:#c6ff00 !important}.mdl-color-text--lime-A700{color:#aeea00 !important}.mdl-color--lime-A700{background-color:#aeea00 !important}.mdl-color-text--yellow{color:#ffeb3b !important}.mdl-color--yellow{background-color:#ffeb3b !important}.mdl-color-text--yellow-50{color:#fffde7 !important}.mdl-color--yellow-50{background-color:#fffde7 !important}.mdl-color-text--yellow-100{color:#fff9c4 !important}.mdl-color--yellow-100{background-color:#fff9c4 !important}.mdl-color-text--yellow-200{color:#fff59d !important}.mdl-color--yellow-200{background-color:#fff59d !important}.mdl-color-text--yellow-300{color:#fff176 !important}.mdl-color--yellow-300{background-color:#fff176 !important}.mdl-color-text--yellow-400{color:#ffee58 !important}.mdl-color--yellow-400{background-color:#ffee58 !important}.mdl-color-text--yellow-500{color:#ffeb3b !important}.mdl-color--yellow-500{background-color:#ffeb3b !important}.mdl-color-text--yellow-600{color:#fdd835 !important}.mdl-color--yellow-600{background-color:#fdd835 !important}.mdl-color-text--yellow-700{color:#fbc02d !important}.mdl-color--yellow-700{background-color:#fbc02d !important}.mdl-color-text--yellow-800{color:#f9a825 !important}.mdl-color--yellow-800{background-color:#f9a825 !important}.mdl-color-text--yellow-900{color:#f57f17 !important}.mdl-color--yellow-900{background-color:#f57f17 !important}.mdl-color-text--yellow-A100{color:#ffff8d !important}.mdl-color--yellow-A100{background-color:#ffff8d !important}.mdl-color-text--yellow-A200{color:#ff0 !important}.mdl-color--yellow-A200{background-color:#ff0 !important}.mdl-color-text--yellow-A400{color:#ffea00 !important}.mdl-color--yellow-A400{background-color:#ffea00 !important}.mdl-color-text--yellow-A700{color:#ffd600 !important}.mdl-color--yellow-A700{background-color:#ffd600 !important}.mdl-color-text--amber{color:#ffc107 !important}.mdl-color--amber{background-color:#ffc107 !important}.mdl-color-text--amber-50{color:#fff8e1 !important}.mdl-color--amber-50{background-color:#fff8e1 !important}.mdl-color-text--amber-100{color:#ffecb3 !important}.mdl-color--amber-100{background-color:#ffecb3 !important}.mdl-color-text--amber-200{color:#ffe082 !important}.mdl-color--amber-200{background-color:#ffe082 !important}.mdl-color-text--amber-300{color:#ffd54f !important}.mdl-color--amber-300{background-color:#ffd54f !important}.mdl-color-text--amber-400{color:#ffca28 !important}.mdl-color--amber-400{background-color:#ffca28 !important}.mdl-color-text--amber-500{color:#ffc107 !important}.mdl-color--amber-500{background-color:#ffc107 !important}.mdl-color-text--amber-600{color:#ffb300 !important}.mdl-color--amber-600{background-color:#ffb300 !important}.mdl-color-text--amber-700{color:#ffa000 !important}.mdl-color--amber-700{background-color:#ffa000 !important}.mdl-color-text--amber-800{color:#ff8f00 !important}.mdl-color--amber-800{background-color:#ff8f00 !important}.mdl-color-text--amber-900{color:#ff6f00 !important}.mdl-color--amber-900{background-color:#ff6f00 !important}.mdl-color-text--amber-A100{color:#ffe57f !important}.mdl-color--amber-A100{background-color:#ffe57f !important}.mdl-color-text--amber-A200{color:#ffd740 !important}.mdl-color--amber-A200{background-color:#ffd740 !important}.mdl-color-text--amber-A400{color:#ffc400 !important}.mdl-color--amber-A400{background-color:#ffc400 !important}.mdl-color-text--amber-A700{color:#ffab00 !important}.mdl-color--amber-A700{background-color:#ffab00 !important}.mdl-color-text--orange{color:#ff9800 !important}.mdl-color--orange{background-color:#ff9800 !important}.mdl-color-text--orange-50{color:#fff3e0 !important}.mdl-color--orange-50{background-color:#fff3e0 !important}.mdl-color-text--orange-100{color:#ffe0b2 !important}.mdl-color--orange-100{background-color:#ffe0b2 !important}.mdl-color-text--orange-200{color:#ffcc80 !important}.mdl-color--orange-200{background-color:#ffcc80 !important}.mdl-color-text--orange-300{color:#ffb74d !important}.mdl-color--orange-300{background-color:#ffb74d !important}.mdl-color-text--orange-400{color:#ffa726 !important}.mdl-color--orange-400{background-color:#ffa726 !important}.mdl-color-text--orange-500{color:#ff9800 !important}.mdl-color--orange-500{background-color:#ff9800 !important}.mdl-color-text--orange-600{color:#fb8c00 !important}.mdl-color--orange-600{background-color:#fb8c00 !important}.mdl-color-text--orange-700{color:#f57c00 !important}.mdl-color--orange-700{background-color:#f57c00 !important}.mdl-color-text--orange-800{color:#ef6c00 !important}.mdl-color--orange-800{background-color:#ef6c00 !important}.mdl-color-text--orange-900{color:#e65100 !important}.mdl-color--orange-900{background-color:#e65100 !important}.mdl-color-text--orange-A100{color:#ffd180 !important}.mdl-color--orange-A100{background-color:#ffd180 !important}.mdl-color-text--orange-A200{color:#ffab40 !important}.mdl-color--orange-A200{background-color:#ffab40 !important}.mdl-color-text--orange-A400{color:#ff9100 !important}.mdl-color--orange-A400{background-color:#ff9100 !important}.mdl-color-text--orange-A700{color:#ff6d00 !important}.mdl-color--orange-A700{background-color:#ff6d00 !important}.mdl-color-text--deep-orange{color:#ff5722 !important}.mdl-color--deep-orange{background-color:#ff5722 !important}.mdl-color-text--deep-orange-50{color:#fbe9e7 !important}.mdl-color--deep-orange-50{background-color:#fbe9e7 !important}.mdl-color-text--deep-orange-100{color:#ffccbc !important}.mdl-color--deep-orange-100{background-color:#ffccbc !important}.mdl-color-text--deep-orange-200{color:#ffab91 !important}.mdl-color--deep-orange-200{background-color:#ffab91 !important}.mdl-color-text--deep-orange-300{color:#ff8a65 !important}.mdl-color--deep-orange-300{background-color:#ff8a65 !important}.mdl-color-text--deep-orange-400{color:#ff7043 !important}.mdl-color--deep-orange-400{background-color:#ff7043 !important}.mdl-color-text--deep-orange-500{color:#ff5722 !important}.mdl-color--deep-orange-500{background-color:#ff5722 !important}.mdl-color-text--deep-orange-600{color:#f4511e !important}.mdl-color--deep-orange-600{background-color:#f4511e !important}.mdl-color-text--deep-orange-700{color:#e64a19 !important}.mdl-color--deep-orange-700{background-color:#e64a19 !important}.mdl-color-text--deep-orange-800{color:#d84315 !important}.mdl-color--deep-orange-800{background-color:#d84315 !important}.mdl-color-text--deep-orange-900{color:#bf360c !important}.mdl-color--deep-orange-900{background-color:#bf360c !important}.mdl-color-text--deep-orange-A100{color:#ff9e80 !important}.mdl-color--deep-orange-A100{background-color:#ff9e80 !important}.mdl-color-text--deep-orange-A200{color:#ff6e40 !important}.mdl-color--deep-orange-A200{background-color:#ff6e40 !important}.mdl-color-text--deep-orange-A400{color:#ff3d00 !important}.mdl-color--deep-orange-A400{background-color:#ff3d00 !important}.mdl-color-text--deep-orange-A700{color:#dd2c00 !important}.mdl-color--deep-orange-A700{background-color:#dd2c00 !important}.mdl-color-text--brown{color:#795548 !important}.mdl-color--brown{background-color:#795548 !important}.mdl-color-text--brown-50{color:#efebe9 !important}.mdl-color--brown-50{background-color:#efebe9 !important}.mdl-color-text--brown-100{color:#d7ccc8 !important}.mdl-color--brown-100{background-color:#d7ccc8 !important}.mdl-color-text--brown-200{color:#bcaaa4 !important}.mdl-color--brown-200{background-color:#bcaaa4 !important}.mdl-color-text--brown-300{color:#a1887f !important}.mdl-color--brown-300{background-color:#a1887f !important}.mdl-color-text--brown-400{color:#8d6e63 !important}.mdl-color--brown-400{background-color:#8d6e63 !important}.mdl-color-text--brown-500{color:#795548 !important}.mdl-color--brown-500{background-color:#795548 !important}.mdl-color-text--brown-600{color:#6d4c41 !important}.mdl-color--brown-600{background-color:#6d4c41 !important}.mdl-color-text--brown-700{color:#5d4037 !important}.mdl-color--brown-700{background-color:#5d4037 !important}.mdl-color-text--brown-800{color:#4e342e !important}.mdl-color--brown-800{background-color:#4e342e !important}.mdl-color-text--brown-900{color:#3e2723 !important}.mdl-color--brown-900{background-color:#3e2723 !important}.mdl-color-text--grey{color:#9e9e9e !important}.mdl-color--grey{background-color:#9e9e9e !important}.mdl-color-text--grey-50{color:#fafafa !important}.mdl-color--grey-50{background-color:#fafafa !important}.mdl-color-text--grey-100{color:#f5f5f5 !important}.mdl-color--grey-100{background-color:#f5f5f5 !important}.mdl-color-text--grey-200{color:#eee !important}.mdl-color--grey-200{background-color:#eee !important}.mdl-color-text--grey-300{color:#e0e0e0 !important}.mdl-color--grey-300{background-color:#e0e0e0 !important}.mdl-color-text--grey-400{color:#bdbdbd !important}.mdl-color--grey-400{background-color:#bdbdbd !important}.mdl-color-text--grey-500{color:#9e9e9e !important}.mdl-color--grey-500{background-color:#9e9e9e !important}.mdl-color-text--grey-600{color:#757575 !important}.mdl-color--grey-600{background-color:#757575 !important}.mdl-color-text--grey-700{color:#616161 !important}.mdl-color--grey-700{background-color:#616161 !important}.mdl-color-text--grey-800{color:#424242 !important}.mdl-color--grey-800{background-color:#424242 !important}.mdl-color-text--grey-900{color:#212121 !important}.mdl-color--grey-900{background-color:#212121 !important}.mdl-color-text--blue-grey{color:#607d8b !important}.mdl-color--blue-grey{background-color:#607d8b !important}.mdl-color-text--blue-grey-50{color:#eceff1 !important}.mdl-color--blue-grey-50{background-color:#eceff1 !important}.mdl-color-text--blue-grey-100{color:#cfd8dc !important}.mdl-color--blue-grey-100{background-color:#cfd8dc !important}.mdl-color-text--blue-grey-200{color:#b0bec5 !important}.mdl-color--blue-grey-200{background-color:#b0bec5 !important}.mdl-color-text--blue-grey-300{color:#90a4ae !important}.mdl-color--blue-grey-300{background-color:#90a4ae !important}.mdl-color-text--blue-grey-400{color:#78909c !important}.mdl-color--blue-grey-400{background-color:#78909c !important}.mdl-color-text--blue-grey-500{color:#607d8b !important}.mdl-color--blue-grey-500{background-color:#607d8b !important}.mdl-color-text--blue-grey-600{color:#546e7a !important}.mdl-color--blue-grey-600{background-color:#546e7a !important}.mdl-color-text--blue-grey-700{color:#455a64 !important}.mdl-color--blue-grey-700{background-color:#455a64 !important}.mdl-color-text--blue-grey-800{color:#37474f !important}.mdl-color--blue-grey-800{background-color:#37474f !important}.mdl-color-text--blue-grey-900{color:#263238 !important}.mdl-color--blue-grey-900{background-color:#263238 !important}.mdl-color--black{background-color:#000 !important}.mdl-color-text--black{color:#000 !important}.mdl-color--white{background-color:#fff !important}.mdl-color-text--white{color:#fff !important}.mdl-color--primary{background-color:rgb(0,150,136)!important}.mdl-color--primary-contrast{background-color:rgb(255,255,255)!important}.mdl-color--primary-dark{background-color:rgb(0,121,107)!important}.mdl-color--accent{background-color:rgb(68,138,255)!important}.mdl-color--accent-contrast{background-color:rgb(255,255,255)!important}.mdl-color-text--primary{color:rgb(0,150,136)!important}.mdl-color-text--primary-contrast{color:rgb(255,255,255)!important}.mdl-color-text--primary-dark{color:rgb(0,121,107)!important}.mdl-color-text--accent{color:rgb(68,138,255)!important}.mdl-color-text--accent-contrast{color:rgb(255,255,255)!important}.mdl-ripple{background:#000;border-radius:50%;height:50px;left:0;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:50px;overflow:hidden}.mdl-ripple.is-animating{transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1);transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1),-webkit-transform .3s cubic-bezier(0,0,.2,1)}.mdl-ripple.is-visible{opacity:.3}.mdl-animation--default,.mdl-animation--fast-out-slow-in{transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-animation--linear-out-slow-in{transition-timing-function:cubic-bezier(0,0,.2,1)}.mdl-animation--fast-out-linear-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.mdl-badge{position:relative;white-space:nowrap;margin-right:24px}.mdl-badge:not([data-badge]){margin-right:auto}.mdl-badge[data-badge]:after{content:attr(data-badge);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:absolute;top:-11px;right:-24px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:600;font-size:12px;width:22px;height:22px;border-radius:50%;background:rgb(68,138,255);color:rgb(255,255,255)}.mdl-button .mdl-badge[data-badge]:after{top:-10px;right:-5px}.mdl-badge.mdl-badge--no-background[data-badge]:after{color:rgb(68,138,255);background:rgba(255,255,255,.2);box-shadow:0 0 1px gray}.mdl-badge.mdl-badge--overlap{margin-right:10px}.mdl-badge.mdl-badge--overlap:after{right:-10px}.mdl-button{background:0 0;border:none;border-radius:2px;color:#000;position:relative;height:36px;margin:0;min-width:64px;padding:0 16px;display:inline-block;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;letter-spacing:0;overflow:hidden;will-change:box-shadow;transition:box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);outline:none;cursor:pointer;text-decoration:none;text-align:center;line-height:36px;vertical-align:middle}.mdl-button::-moz-focus-inner{border:0}.mdl-button:hover{background-color:rgba(158,158,158,.2)}.mdl-button:focus:not(:active){background-color:rgba(0,0,0,.12)}.mdl-button:active{background-color:rgba(158,158,158,.4)}.mdl-button.mdl-button--colored{color:rgb(0,150,136)}.mdl-button.mdl-button--colored:focus:not(:active){background-color:rgba(0,0,0,.12)}input.mdl-button[type="submit"]{-webkit-appearance:none}.mdl-button--raised{background:rgba(158,158,158,.2);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-button--raised:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--raised:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--raised.mdl-button--colored{background:rgb(0,150,136);color:rgb(255,255,255)}.mdl-button--raised.mdl-button--colored:hover{background-color:rgb(0,150,136)}.mdl-button--raised.mdl-button--colored:active{background-color:rgb(0,150,136)}.mdl-button--raised.mdl-button--colored:focus:not(:active){background-color:rgb(0,150,136)}.mdl-button--raised.mdl-button--colored .mdl-ripple{background:rgb(255,255,255)}.mdl-button--fab{border-radius:50%;font-size:24px;height:56px;margin:auto;min-width:56px;width:56px;padding:0;overflow:hidden;background:rgba(158,158,158,.2);box-shadow:0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);position:relative;line-height:normal}.mdl-button--fab .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--fab.mdl-button--mini-fab{height:40px;min-width:40px;width:40px}.mdl-button--fab .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button--fab:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--fab:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--fab.mdl-button--colored{background:rgb(68,138,255);color:rgb(255,255,255)}.mdl-button--fab.mdl-button--colored:hover{background-color:rgb(68,138,255)}.mdl-button--fab.mdl-button--colored:focus:not(:active){background-color:rgb(68,138,255)}.mdl-button--fab.mdl-button--colored:active{background-color:rgb(68,138,255)}.mdl-button--fab.mdl-button--colored .mdl-ripple{background:rgb(255,255,255)}.mdl-button--icon{border-radius:50%;font-size:24px;height:32px;margin-left:0;margin-right:0;min-width:32px;width:32px;padding:0;overflow:hidden;color:inherit;line-height:normal}.mdl-button--icon .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon{height:24px;min-width:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon .material-icons{top:0;left:0}.mdl-button--icon .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button__ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-button[disabled] .mdl-button__ripple-container .mdl-ripple,.mdl-button.mdl-button--disabled .mdl-button__ripple-container .mdl-ripple{background-color:transparent}.mdl-button--primary.mdl-button--primary{color:rgb(0,150,136)}.mdl-button--primary.mdl-button--primary .mdl-ripple{background:rgb(255,255,255)}.mdl-button--primary.mdl-button--primary.mdl-button--raised,.mdl-button--primary.mdl-button--primary.mdl-button--fab{color:rgb(255,255,255);background-color:rgb(0,150,136)}.mdl-button--accent.mdl-button--accent{color:rgb(68,138,255)}.mdl-button--accent.mdl-button--accent .mdl-ripple{background:rgb(255,255,255)}.mdl-button--accent.mdl-button--accent.mdl-button--raised,.mdl-button--accent.mdl-button--accent.mdl-button--fab{color:rgb(255,255,255);background-color:rgb(68,138,255)}.mdl-button[disabled][disabled],.mdl-button.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26);cursor:default;background-color:transparent}.mdl-button--fab[disabled][disabled],.mdl-button--fab.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-button--raised[disabled][disabled],.mdl-button--raised.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26);box-shadow:none}.mdl-button--colored[disabled][disabled],.mdl-button--colored.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26)}.mdl-button .material-icons{vertical-align:middle}.mdl-card{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;font-size:16px;font-weight:400;min-height:200px;overflow:hidden;width:330px;z-index:1;position:relative;background:#fff;border-radius:2px;box-sizing:border-box}.mdl-card__media{background-color:rgb(68,138,255);background-repeat:repeat;background-position:50% 50%;background-size:cover;background-origin:padding-box;background-attachment:scroll;box-sizing:border-box}.mdl-card__title{-webkit-align-items:center;-ms-flex-align:center;align-items:center;color:#000;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;line-height:normal;padding:16px;-webkit-perspective-origin:165px 56px;perspective-origin:165px 56px;-webkit-transform-origin:165px 56px;transform-origin:165px 56px;box-sizing:border-box}.mdl-card__title.mdl-card--border{border-bottom:1px solid rgba(0,0,0,.1)}.mdl-card__title-text{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;color:inherit;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:24px;font-weight:300;line-height:normal;overflow:hidden;-webkit-transform-origin:149px 48px;transform-origin:149px 48px;margin:0}.mdl-card__subtitle-text{font-size:14px;color:rgba(0,0,0,.54);margin:0}.mdl-card__supporting-text{color:rgba(0,0,0,.54);font-size:1rem;line-height:18px;overflow:hidden;padding:16px;width:90%}.mdl-card__actions{font-size:16px;line-height:normal;width:100%;background-color:transparent;padding:8px;box-sizing:border-box}.mdl-card__actions.mdl-card--border{border-top:1px solid rgba(0,0,0,.1)}.mdl-card--expand{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-card__menu{position:absolute;right:16px;top:16px}.mdl-checkbox{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0}.mdl-checkbox.is-upgraded{padding-left:24px}.mdl-checkbox__input{line-height:24px}.mdl-checkbox.is-upgraded .mdl-checkbox__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-checkbox__box-outline{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;overflow:hidden;border:2px solid rgba(0,0,0,.54);border-radius:2px;z-index:2}.mdl-checkbox.is-checked .mdl-checkbox__box-outline{border:2px solid rgb(0,150,136)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__box-outline,.mdl-checkbox.is-disabled .mdl-checkbox__box-outline{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__focus-helper{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:transparent}.mdl-checkbox.is-focused .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-checkbox.is-focused.is-checked .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(0,150,136,.26);background-color:rgba(0,150,136,.26)}.mdl-checkbox__tick-outline{position:absolute;top:0;left:0;height:100%;width:100%;-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");background:0 0;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background}.mdl-checkbox.is-checked .mdl-checkbox__tick-outline{background:rgb(0,150,136)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}fieldset[disabled] .mdl-checkbox.is-checked .mdl-checkbox__tick-outline,.mdl-checkbox.is-checked.is-disabled .mdl-checkbox__tick-outline{background:rgba(0,0,0,.26)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}.mdl-checkbox__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0}fieldset[disabled] .mdl-checkbox .mdl-checkbox__label,.mdl-checkbox.is-disabled .mdl-checkbox__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__ripple-container{position:absolute;z-index:2;top:-6px;left:-10px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-checkbox__ripple-container .mdl-ripple{background:rgb(0,150,136)}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container{cursor:auto}fieldset[disabled] .mdl-checkbox .mdl-checkbox__ripple-container .mdl-ripple,.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container .mdl-ripple{background:0 0}.mdl-chip{height:32px;font-family:"Roboto","Helvetica","Arial",sans-serif;line-height:32px;padding:0 12px;border:0;border-radius:16px;background-color:#dedede;display:inline-block;color:rgba(0,0,0,.87);margin:2px 0;font-size:0;white-space:nowrap}.mdl-chip__text{font-size:13px;vertical-align:middle;display:inline-block}.mdl-chip__action{height:24px;width:24px;background:0 0;opacity:.54;cursor:pointer;padding:0;margin:0 0 0 4px;font-size:13px;text-decoration:none;color:rgba(0,0,0,.87);border:none;outline:none}.mdl-chip__action,.mdl-chip__contact{display:inline-block;vertical-align:middle;overflow:hidden;text-align:center}.mdl-chip__contact{height:32px;width:32px;border-radius:16px;margin-right:8px;font-size:18px;line-height:32px}.mdl-chip:focus{outline:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-chip:active{background-color:#d6d6d6}.mdl-chip--deletable{padding-right:4px}.mdl-chip--contact{padding-left:0}.mdl-data-table{position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff}.mdl-data-table thead{padding-bottom:3px}.mdl-data-table thead .mdl-data-table__select{margin-top:0}.mdl-data-table tbody tr{position:relative;height:48px;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:background-color}.mdl-data-table tbody tr.is-selected{background-color:#e0e0e0}.mdl-data-table tbody tr:hover{background-color:#eee}.mdl-data-table td{text-align:right}.mdl-data-table th{padding:0 18px 12px 18px;text-align:right}.mdl-data-table td:first-of-type,.mdl-data-table th:first-of-type{padding-left:24px}.mdl-data-table td:last-of-type,.mdl-data-table th:last-of-type{padding-right:24px}.mdl-data-table td{position:relative;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px;box-sizing:border-box}.mdl-data-table td,.mdl-data-table td .mdl-data-table__select{vertical-align:middle}.mdl-data-table th{position:relative;vertical-align:bottom;text-overflow:ellipsis;font-weight:700;line-height:24px;letter-spacing:0;height:48px;font-size:12px;color:rgba(0,0,0,.54);padding-bottom:8px;box-sizing:border-box}.mdl-data-table th.mdl-data-table__header--sorted-ascending,.mdl-data-table th.mdl-data-table__header--sorted-descending{color:rgba(0,0,0,.87)}.mdl-data-table th.mdl-data-table__header--sorted-ascending:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:before{font-family:'Material Icons';font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;word-wrap:normal;-moz-font-feature-settings:'liga';font-feature-settings:'liga';-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased;font-size:16px;content:"\e5d8";margin-right:5px;vertical-align:sub}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover{cursor:pointer}.mdl-data-table th.mdl-data-table__header--sorted-ascending:hover:before,.mdl-data-table th.mdl-data-table__header--sorted-descending:hover:before{color:rgba(0,0,0,.26)}.mdl-data-table th.mdl-data-table__header--sorted-descending:before{content:"\e5db"}.mdl-data-table__select{width:16px}.mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric{text-align:left}.mdl-dialog{border:none;box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2);width:280px}.mdl-dialog__title{padding:24px 24px 0;margin:0;font-size:2.5rem}.mdl-dialog__actions{padding:8px 8px 8px 24px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.mdl-dialog__actions>*{margin-right:8px;height:36px}.mdl-dialog__actions>*:first-child{margin-right:0}.mdl-dialog__actions--full-width{padding:0 0 8px}.mdl-dialog__actions--full-width>*{height:48px;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;padding-right:16px;margin-right:0;text-align:right}.mdl-dialog__content{padding:20px 24px 24px;color:rgba(0,0,0,.54)}.mdl-mega-footer{padding:16px 40px;color:#9e9e9e;background-color:#424242}.mdl-mega-footer--top-section:after,.mdl-mega-footer--middle-section:after,.mdl-mega-footer--bottom-section:after,.mdl-mega-footer__top-section:after,.mdl-mega-footer__middle-section:after,.mdl-mega-footer__bottom-section:after{content:'';display:block;clear:both}.mdl-mega-footer--left-section,.mdl-mega-footer__left-section,.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{margin-bottom:16px}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:block;margin-bottom:16px;color:inherit;text-decoration:none}@media screen and (min-width:760px){.mdl-mega-footer--left-section,.mdl-mega-footer__left-section{float:left}.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{float:right}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:inline-block;margin-left:16px;line-height:36px;vertical-align:middle}}.mdl-mega-footer--social-btn,.mdl-mega-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{display:block;position:relative}@media screen and (min-width:760px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{width:33%}.mdl-mega-footer--drop-down-section:nth-child(1),.mdl-mega-footer--drop-down-section:nth-child(2),.mdl-mega-footer__drop-down-section:nth-child(1),.mdl-mega-footer__drop-down-section:nth-child(2){float:left}.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(3){float:right}.mdl-mega-footer--drop-down-section:nth-child(3):after,.mdl-mega-footer__drop-down-section:nth-child(3):after{clear:right}.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section:nth-child(4){clear:right;float:right}.mdl-mega-footer--middle-section:after,.mdl-mega-footer__middle-section:after{content:'';display:block;clear:both}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:0}}@media screen and (min-width:1024px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section,.mdl-mega-footer__drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(4){width:24%;float:left}}.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{position:absolute;width:100%;height:55.8px;padding:32px;margin:-16px 0 0;cursor:pointer;z-index:1;opacity:0}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CE'}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list{display:none}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CF'}.mdl-mega-footer--heading,.mdl-mega-footer__heading{position:relative;width:100%;padding-right:39.8px;margin-bottom:16px;box-sizing:border-box;font-size:14px;line-height:23.8px;font-weight:500;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#e0e0e0}.mdl-mega-footer--heading:after,.mdl-mega-footer__heading:after{content:'';position:absolute;top:0;right:0;display:block;width:23.8px;height:23.8px;background-size:cover}.mdl-mega-footer--link-list,.mdl-mega-footer__link-list{list-style:none;padding:0;margin:0 0 32px}.mdl-mega-footer--link-list:after,.mdl-mega-footer__link-list:after{clear:both;display:block;content:''}.mdl-mega-footer--link-list li,.mdl-mega-footer__link-list li{font-size:14px;font-weight:400;letter-spacing:0;line-height:20px}.mdl-mega-footer--link-list a,.mdl-mega-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}@media screen and (min-width:760px){.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{display:none}.mdl-mega-footer--heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox+.mdl-mega-footer__heading:after{content:''}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--link-list,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading+.mdl-mega-footer__link-list,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading+.mdl-mega-footer--link-list{display:block}.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked+.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked+.mdl-mega-footer__heading:after{content:''}}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:16px;margin-bottom:16px}.mdl-logo{margin-bottom:16px;color:#fff}.mdl-mega-footer--bottom-section .mdl-mega-footer--link-list li,.mdl-mega-footer__bottom-section .mdl-mega-footer__link-list li{float:left;margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-logo{float:left;margin-bottom:0;margin-right:16px}}.mdl-mini-footer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:32px 16px;color:#9e9e9e;background-color:#424242}.mdl-mini-footer:after{content:'';display:block}.mdl-mini-footer .mdl-logo{line-height:36px}.mdl-mini-footer--link-list,.mdl-mini-footer__link-list{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;list-style:none;margin:0;padding:0}.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{line-height:36px}}.mdl-mini-footer--link-list a,.mdl-mini-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}.mdl-mini-footer--left-section,.mdl-mini-footer__left-section{display:inline-block;-webkit-order:0;-ms-flex-order:0;order:0}.mdl-mini-footer--right-section,.mdl-mini-footer__right-section{display:inline-block;-webkit-order:1;-ms-flex-order:1;order:1}.mdl-mini-footer--social-btn,.mdl-mini-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-icon-toggle{position:relative;z-index:1;vertical-align:middle;display:inline-block;height:32px;margin:0;padding:0}.mdl-icon-toggle__input{line-height:32px}.mdl-icon-toggle.is-upgraded .mdl-icon-toggle__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-icon-toggle__label{display:inline-block;position:relative;cursor:pointer;height:32px;width:32px;min-width:32px;color:#616161;border-radius:50%;padding:0;margin-left:0;margin-right:0;text-align:center;background-color:transparent;will-change:background-color;transition:background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1)}.mdl-icon-toggle__label.material-icons{line-height:32px;font-size:24px}.mdl-icon-toggle.is-checked .mdl-icon-toggle__label{color:rgb(0,150,136)}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__label{color:rgba(0,0,0,.26);cursor:auto;transition:none}.mdl-icon-toggle.is-focused .mdl-icon-toggle__label{background-color:rgba(0,0,0,.12)}.mdl-icon-toggle.is-focused.is-checked .mdl-icon-toggle__label{background-color:rgba(0,150,136,.26)}.mdl-icon-toggle__ripple-container{position:absolute;z-index:2;top:-2px;left:-2px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-icon-toggle__ripple-container .mdl-ripple{background:#616161}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container{cursor:auto}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container .mdl-ripple{background:0 0}.mdl-list{display:block;padding:8px 0;list-style:none}.mdl-list__item{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;letter-spacing:.04em;line-height:1;min-height:48px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;padding:16px;cursor:default;color:rgba(0,0,0,.87);overflow:hidden}.mdl-list__item,.mdl-list__item .mdl-list__item-primary-content{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.mdl-list__item .mdl-list__item-primary-content{-webkit-order:0;-ms-flex-order:0;order:0;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2;text-decoration:none}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-icon{margin-right:32px}.mdl-list__item .mdl-list__item-primary-content .mdl-list__item-avatar{margin-right:16px}.mdl-list__item .mdl-list__item-secondary-content{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column;-ms-flex-flow:column;flex-flow:column;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;margin-left:16px}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-action label{display:inline}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-secondary-info{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;color:rgba(0,0,0,.54)}.mdl-list__item .mdl-list__item-secondary-content .mdl-list__item-sub-header{padding:0 0 0 16px}.mdl-list__item-icon,.mdl-list__item-icon.material-icons{height:24px;width:24px;font-size:24px;box-sizing:border-box;color:#757575}.mdl-list__item-avatar,.mdl-list__item-avatar.material-icons{height:40px;width:40px;box-sizing:border-box;border-radius:50%;background-color:#757575;font-size:40px;color:#fff}.mdl-list__item--two-line{height:72px}.mdl-list__item--two-line .mdl-list__item-primary-content{height:36px;line-height:20px;display:block}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-avatar{float:left}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left;margin-top:6px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-secondary-content{height:36px}.mdl-list__item--two-line .mdl-list__item-primary-content .mdl-list__item-sub-title{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-list__item--three-line{height:88px}.mdl-list__item--three-line .mdl-list__item-primary-content{height:52px;line-height:20px;display:block}.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-avatar,.mdl-list__item--three-line .mdl-list__item-primary-content .mdl-list__item-icon{float:left}.mdl-list__item--three-line .mdl-list__item-secondary-content{height:52px}.mdl-list__item--three-line .mdl-list__item-text-body{font-size:14px;font-weight:400;letter-spacing:0;line-height:18px;height:52px;color:rgba(0,0,0,.54);display:block;padding:0}.mdl-menu__container{display:block;margin:0;padding:0;border:none;position:absolute;overflow:visible;height:0;width:0;visibility:hidden;z-index:-1}.mdl-menu__container.is-visible,.mdl-menu__container.is-animating{z-index:999;visibility:visible}.mdl-menu__outline{display:block;background:#fff;margin:0;padding:0;border:none;border-radius:2px;position:absolute;top:0;left:0;overflow:hidden;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;transform-origin:0 0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);will-change:transform;transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1);z-index:-1}.mdl-menu__container.is-visible .mdl-menu__outline{opacity:1;-webkit-transform:scale(1);transform:scale(1);z-index:999}.mdl-menu__outline.mdl-menu--bottom-right{-webkit-transform-origin:100% 0;transform-origin:100% 0}.mdl-menu__outline.mdl-menu--top-left{-webkit-transform-origin:0 100%;transform-origin:0 100%}.mdl-menu__outline.mdl-menu--top-right{-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.mdl-menu{position:absolute;list-style:none;top:0;left:0;height:auto;width:auto;min-width:124px;padding:8px 0;margin:0;opacity:0;clip:rect(0 0 0 0);z-index:-1}.mdl-menu__container.is-visible .mdl-menu{opacity:1;z-index:999}.mdl-menu.is-animating{transition:opacity .2s cubic-bezier(.4,0,.2,1),clip .3s cubic-bezier(.4,0,.2,1)}.mdl-menu.mdl-menu--bottom-right{left:auto;right:0}.mdl-menu.mdl-menu--top-left{top:auto;bottom:0}.mdl-menu.mdl-menu--top-right{top:auto;left:auto;bottom:0;right:0}.mdl-menu.mdl-menu--unaligned{top:auto;left:auto}.mdl-menu__item{display:block;border:none;color:rgba(0,0,0,.87);background-color:transparent;text-align:left;margin:0;padding:0 16px;outline-color:#bdbdbd;position:relative;overflow:hidden;font-size:14px;font-weight:400;letter-spacing:0;text-decoration:none;cursor:pointer;height:48px;line-height:48px;white-space:nowrap;opacity:0;transition:opacity .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-menu__container.is-visible .mdl-menu__item{opacity:1}.mdl-menu__item::-moz-focus-inner{border:0}.mdl-menu__item--full-bleed-divider{border-bottom:1px solid rgba(0,0,0,.12)}.mdl-menu__item[disabled],.mdl-menu__item[data-mdl-disabled]{color:#bdbdbd;background-color:transparent;cursor:auto}.mdl-menu__item[disabled]:hover,.mdl-menu__item[data-mdl-disabled]:hover{background-color:transparent}.mdl-menu__item[disabled]:focus,.mdl-menu__item[data-mdl-disabled]:focus{background-color:transparent}.mdl-menu__item[disabled] .mdl-ripple,.mdl-menu__item[data-mdl-disabled] .mdl-ripple{background:0 0}.mdl-menu__item:hover{background-color:#eee}.mdl-menu__item:focus{outline:none;background-color:#eee}.mdl-menu__item:active{background-color:#e0e0e0}.mdl-menu__item--ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-progress{display:block;position:relative;height:4px;width:500px;max-width:100%}.mdl-progress>.bar{display:block;position:absolute;top:0;bottom:0;width:0%;transition:width .2s cubic-bezier(.4,0,.2,1)}.mdl-progress>.progressbar{background-color:rgb(0,150,136);z-index:1;left:0}.mdl-progress>.bufferbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(0,150,136),rgb(0,150,136));z-index:0;left:0}.mdl-progress>.auxbar{right:0}@supports (-webkit-appearance:none){.mdl-progress:not(.mdl-progress--indeterminate):not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate):not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.7),rgba(255,255,255,.7)),linear-gradient(to right,rgb(0,150,136),rgb(0,150,136));-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=")}}.mdl-progress:not(.mdl-progress--indeterminate)>.auxbar,.mdl-progress:not(.mdl-progress__indeterminate)>.auxbar{background-image:linear-gradient(to right,rgba(255,255,255,.9),rgba(255,255,255,.9)),linear-gradient(to right,rgb(0,150,136),rgb(0,150,136))}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1{-webkit-animation-name:indeterminate1;animation-name:indeterminate1}.mdl-progress.mdl-progress--indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar1,.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-color:rgb(0,150,136);-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.mdl-progress.mdl-progress--indeterminate>.bar3,.mdl-progress.mdl-progress__indeterminate>.bar3{background-image:none;-webkit-animation-name:indeterminate2;animation-name:indeterminate2}@-webkit-keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@-webkit-keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}@keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}.mdl-navigation{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;box-sizing:border-box}.mdl-navigation__link{color:#424242;text-decoration:none;margin:0;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0;opacity:.87}.mdl-navigation__link .material-icons{vertical-align:middle}.mdl-layout{width:100%;height:100%;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow-y:auto;overflow-x:hidden;position:relative;-webkit-overflow-scrolling:touch}.mdl-layout.is-small-screen .mdl-layout--large-screen-only{display:none}.mdl-layout:not(.is-small-screen) .mdl-layout--small-screen-only{display:none}.mdl-layout__container{position:absolute;width:100%;height:100%}.mdl-layout__title,.mdl-layout-title{display:block;position:relative;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400;box-sizing:border-box}.mdl-layout-spacer{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-layout__drawer{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:240px;height:100%;max-height:100%;position:absolute;top:0;left:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);box-sizing:border-box;border-right:1px solid #e0e0e0;background:#fafafa;-webkit-transform:translateX(-250px);transform:translateX(-250px);-webkit-transform-style:preserve-3d;transform-style:preserve-3d;will-change:transform;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;color:#424242;overflow:visible;overflow-y:auto;z-index:5}.mdl-layout__drawer.is-visible{-webkit-transform:translateX(0);transform:translateX(0)}.mdl-layout__drawer.is-visible~.mdl-layout__content.mdl-layout__content{overflow:hidden}.mdl-layout__drawer>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:64px;padding-left:40px}@media screen and (max-width:1024px){.mdl-layout__drawer>.mdl-layout__title,.mdl-layout__drawer>.mdl-layout-title{line-height:56px;padding-left:16px}}.mdl-layout__drawer .mdl-navigation{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:stretch;-ms-flex-align:stretch;-ms-grid-row-align:stretch;align-items:stretch;padding-top:16px}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{display:block;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;padding:16px 40px;margin:0;color:#757575}@media screen and (max-width:1024px){.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{padding:16px}}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link:hover{background-color:#e0e0e0}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link--current{background-color:#e0e0e0;color:#000}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__drawer{-webkit-transform:translateX(0);transform:translateX(0)}}.mdl-layout__drawer-button{display:block;position:absolute;height:48px;width:48px;border:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden;text-align:center;cursor:pointer;font-size:26px;line-height:56px;font-family:Helvetica,Arial,sans-serif;margin:8px 12px;top:0;left:0;color:rgb(255,255,255);z-index:4}.mdl-layout__header .mdl-layout__drawer-button{position:absolute;color:rgb(255,255,255);background-color:inherit}@media screen and (max-width:1024px){.mdl-layout__header .mdl-layout__drawer-button{margin:4px}}@media screen and (max-width:1024px){.mdl-layout__drawer-button{margin:4px;color:rgba(0,0,0,.5)}}@media screen and (min-width:1025px){.mdl-layout__drawer-button{line-height:54px}.mdl-layout--no-desktop-drawer-button .mdl-layout__drawer-button,.mdl-layout--fixed-drawer>.mdl-layout__drawer-button,.mdl-layout--no-drawer-button .mdl-layout__drawer-button{display:none}}.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;box-sizing:border-box;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;width:100%;margin:0;padding:0;border:none;min-height:64px;max-height:1000px;z-index:3;background-color:rgb(0,150,136);color:rgb(255,255,255);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:max-height,box-shadow}@media screen and (max-width:1024px){.mdl-layout__header{min-height:56px}}.mdl-layout--fixed-drawer.is-upgraded:not(.is-small-screen)>.mdl-layout__header{margin-left:240px;width:calc(100% - 240px)}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__header .mdl-layout__header-row{padding-left:40px}}.mdl-layout__header>.mdl-layout-icon{position:absolute;left:40px;top:16px;height:32px;width:32px;overflow:hidden;z-index:3;display:block}@media screen and (max-width:1024px){.mdl-layout__header>.mdl-layout-icon{left:16px;top:12px}}.mdl-layout.has-drawer .mdl-layout__header>.mdl-layout-icon{display:none}.mdl-layout__header.is-compact{max-height:64px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact{max-height:56px}}.mdl-layout__header.is-compact.has-tabs{height:112px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact.has-tabs{min-height:104px}}@media screen and (max-width:1024px){.mdl-layout__header{display:none}.mdl-layout--fixed-header>.mdl-layout__header{display:-webkit-flex;display:-ms-flexbox;display:flex}}.mdl-layout__header--transparent.mdl-layout__header--transparent{background-color:transparent;box-shadow:none}.mdl-layout__header--seamed,.mdl-layout__header--scroll{box-shadow:none}.mdl-layout__header--waterfall{box-shadow:none;overflow:hidden}.mdl-layout__header--waterfall.is-casting-shadow{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-layout__header--waterfall.mdl-layout__header--waterfall-hide-top{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.mdl-layout__header-row{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:64px;margin:0;padding:0 40px 0 80px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:40px}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__header-row{padding-left:40px}}@media screen and (max-width:1024px){.mdl-layout__header-row{height:56px;padding:0 16px 0 72px}.mdl-layout--no-drawer-button .mdl-layout__header-row{padding-left:16px}}.mdl-layout__header-row>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__header--scroll .mdl-layout__header-row{width:100%}.mdl-layout__header-row .mdl-navigation{margin:0;padding:0;height:64px;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation{height:56px}}.mdl-layout__header-row .mdl-navigation__link{display:block;color:rgb(255,255,255);line-height:64px;padding:0 24px}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation__link{line-height:56px;padding:0 16px}}.mdl-layout__obfuscator{background-color:transparent;position:absolute;top:0;left:0;height:100%;width:100%;z-index:4;visibility:hidden;transition-property:background-color;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-layout__obfuscator.is-visible{background-color:rgba(0,0,0,.5);visibility:visible}@supports (pointer-events:auto){.mdl-layout__obfuscator{background-color:rgba(0,0,0,.5);opacity:0;transition-property:opacity;visibility:visible;pointer-events:none}.mdl-layout__obfuscator.is-visible{pointer-events:auto;opacity:1}}.mdl-layout__content{-ms-flex:0 1 auto;position:relative;display:inline-block;overflow-y:auto;overflow-x:hidden;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;z-index:1;-webkit-overflow-scrolling:touch}.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:240px}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow:visible}@media screen and (max-width:1024px){.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:0}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow-y:auto;overflow-x:hidden}}.mdl-layout__tab-bar{height:96px;margin:0;width:calc(100% - 112px);padding:0 0 0 56px;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:rgb(0,150,136);overflow-y:hidden;overflow-x:scroll}.mdl-layout__tab-bar::-webkit-scrollbar{display:none}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}@media screen and (min-width:1025px){.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar{padding-left:16px;width:calc(100% - 32px)}}@media screen and (max-width:1024px){.mdl-layout__tab-bar{width:calc(100% - 60px);padding:0 0 0 60px}.mdl-layout--no-drawer-button .mdl-layout__tab-bar{width:calc(100% - 8px);padding-left:4px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar{padding:0;overflow:hidden;width:100%}.mdl-layout__tab-bar-container{position:relative;height:48px;width:100%;border:none;margin:0;z-index:2;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden}.mdl-layout__container>.mdl-layout__tab-bar-container{position:absolute;top:0;left:0}.mdl-layout__tab-bar-button{display:inline-block;position:absolute;top:0;height:48px;width:56px;z-index:4;text-align:center;background-color:rgb(0,150,136);color:transparent;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button{width:16px}.mdl-layout--no-desktop-drawer-button .mdl-layout__tab-bar-button .material-icons,.mdl-layout--no-drawer-button .mdl-layout__tab-bar-button .material-icons{position:relative;left:-4px}@media screen and (max-width:1024px){.mdl-layout__tab-bar-button{width:60px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar-button{display:none}.mdl-layout__tab-bar-button .material-icons{line-height:48px}.mdl-layout__tab-bar-button.is-active{color:rgb(255,255,255)}.mdl-layout__tab-bar-left-button{left:0}.mdl-layout__tab-bar-right-button{right:0}.mdl-layout__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(255,255,255,.6);overflow:hidden}@media screen and (max-width:1024px){.mdl-layout__tab{padding:0 12px}}.mdl-layout--fixed-tabs .mdl-layout__tab{float:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;padding:0}.mdl-layout.is-upgraded .mdl-layout__tab.is-active{color:rgb(255,255,255)}.mdl-layout.is-upgraded .mdl-layout__tab.is-active::after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(68,138,255);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-layout__tab .mdl-layout__tab-ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-layout__tab .mdl-layout__tab-ripple-container .mdl-ripple{background-color:rgb(255,255,255)}.mdl-layout__tab-panel{display:block}.mdl-layout.is-upgraded .mdl-layout__tab-panel{display:none}.mdl-layout.is-upgraded .mdl-layout__tab-panel.is-active{display:block}.mdl-radio{position:relative;font-size:16px;line-height:24px;display:inline-block;box-sizing:border-box;margin:0;padding-left:0}.mdl-radio.is-upgraded{padding-left:24px}.mdl-radio__button{line-height:24px}.mdl-radio.is-upgraded .mdl-radio__button{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-radio__outer-circle{position:absolute;top:4px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;border:2px solid rgba(0,0,0,.54);border-radius:50%;z-index:2}.mdl-radio.is-checked .mdl-radio__outer-circle{border:2px solid rgb(0,150,136)}.mdl-radio__outer-circle fieldset[disabled] .mdl-radio,.mdl-radio.is-disabled .mdl-radio__outer-circle{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-radio__inner-circle{position:absolute;z-index:1;margin:0;top:8px;left:4px;box-sizing:border-box;width:8px;height:8px;cursor:pointer;transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:transform;transition-property:transform,-webkit-transform;-webkit-transform:scale3d(0,0,0);transform:scale3d(0,0,0);border-radius:50%;background:rgb(0,150,136)}.mdl-radio.is-checked .mdl-radio__inner-circle{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}fieldset[disabled] .mdl-radio .mdl-radio__inner-circle,.mdl-radio.is-disabled .mdl-radio__inner-circle{background:rgba(0,0,0,.26);cursor:auto}.mdl-radio.is-focused .mdl-radio__inner-circle{box-shadow:0 0 0 10px rgba(0,0,0,.1)}.mdl-radio__label{cursor:pointer}fieldset[disabled] .mdl-radio .mdl-radio__label,.mdl-radio.is-disabled .mdl-radio__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-radio__ripple-container{position:absolute;z-index:2;top:-9px;left:-13px;box-sizing:border-box;width:42px;height:42px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-radio__ripple-container .mdl-ripple{background:rgb(0,150,136)}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container,.mdl-radio.is-disabled .mdl-radio__ripple-container{cursor:auto}fieldset[disabled] .mdl-radio .mdl-radio__ripple-container .mdl-ripple,.mdl-radio.is-disabled .mdl-radio__ripple-container .mdl-ripple{background:0 0}_:-ms-input-placeholder,:root .mdl-slider.mdl-slider.is-upgraded{-ms-appearance:none;height:32px;margin:0}.mdl-slider{width:calc(100% - 40px);margin:0 20px}.mdl-slider.is-upgraded{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:2px;background:0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:0;padding:0;color:rgb(0,150,136);-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;z-index:1;cursor:pointer}.mdl-slider.is-upgraded::-moz-focus-outer{border:0}.mdl-slider.is-upgraded::-ms-tooltip{display:none}.mdl-slider.is-upgraded::-webkit-slider-runnable-track{background:0 0}.mdl-slider.is-upgraded::-moz-range-track{background:0 0;border:none}.mdl-slider.is-upgraded::-ms-track{background:0 0;color:transparent;height:2px;width:100%;border:none}.mdl-slider.is-upgraded::-ms-fill-lower{padding:0;background:linear-gradient(to right,transparent,transparent 16px,rgb(0,150,136)16px,rgb(0,150,136)0)}.mdl-slider.is-upgraded::-ms-fill-upper{padding:0;background:linear-gradient(to left,transparent,transparent 16px,rgba(0,0,0,.26)16px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background:rgb(0,150,136);border:none;transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded::-moz-range-thumb{-moz-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background-image:none;background:rgb(0,150,136);border:none}.mdl-slider.is-upgraded:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,150,136,.26)}.mdl-slider.is-upgraded:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,150,136,.26)}.mdl-slider.is-upgraded:active::-webkit-slider-thumb{background-image:none;background:rgb(0,150,136);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded:active::-moz-range-thumb{background-image:none;background:rgb(0,150,136);transform:scale(1.5)}.mdl-slider.is-upgraded::-ms-thumb{width:32px;height:32px;border:none;border-radius:50%;background:rgb(0,150,136);transform:scale(.375);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1),-webkit-transform .18s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgb(0,150,136)0%,rgb(0,150,136)37.5%,rgba(0,150,136,.26)37.5%,rgba(0,150,136,.26)100%);transform:scale(1)}.mdl-slider.is-upgraded:active::-ms-thumb{background:rgb(0,150,136);transform:scale(.5625)}.mdl-slider.is-upgraded.is-lowest-value::-webkit-slider-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-moz-range-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:active::-webkit-slider-thumb{border:1.6px solid rgba(0,0,0,.26);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:9px}.mdl-slider.is-upgraded.is-lowest-value:active::-moz-range-thumb{border:1.5px solid rgba(0,0,0,.26);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value::-ms-thumb{background:radial-gradient(circle closest-side,transparent 0%,transparent 66.67%,rgba(0,0,0,.26)66.67%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgba(0,0,0,.12)0%,rgba(0,0,0,.12)25%,rgba(0,0,0,.26)25%,rgba(0,0,0,.26)37.5%,rgba(0,0,0,.12)37.5%,rgba(0,0,0,.12)100%);transform:scale(1)}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-thumb{transform:scale(.5625);background:radial-gradient(circle closest-side,transparent 0%,transparent 77.78%,rgba(0,0,0,.26)77.78%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-lower{background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-fill-upper{margin-left:9px}.mdl-slider.is-upgraded:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled::-webkit-slider-thumb{-webkit-transform:scale(.667);transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded:disabled::-moz-range-thumb{transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-lower{background-color:rgba(0,0,0,.26);left:-6px}.mdl-slider.is-upgraded:disabled+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-webkit-slider-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;-webkit-transform:scale(.667);transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-moz-range-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:active+.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded:disabled:active::-ms-thumb,.mdl-slider.is-upgraded:disabled::-ms-thumb{transform:scale(.25);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-ms-thumb{transform:scale(.25);background:radial-gradient(circle closest-side,transparent 0%,transparent 50%,rgba(0,0,0,.26)50%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded:disabled::-ms-fill-lower{margin-right:6px;background:linear-gradient(to right,transparent,transparent 25px,rgba(0,0,0,.26)25px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded:disabled::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-fill-upper{margin-left:6px}.mdl-slider__ie-container{height:18px;overflow:visible;border:none;margin:none;padding:none}.mdl-slider__container{height:18px;position:relative;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.mdl-slider__container,.mdl-slider__background-flex{background:0 0;display:-webkit-flex;display:-ms-flexbox;display:flex}.mdl-slider__background-flex{position:absolute;height:2px;width:calc(100% - 52px);top:50%;left:0;margin:0 26px;overflow:hidden;border:0;padding:0;-webkit-transform:translate(0,-1px);transform:translate(0,-1px)}.mdl-slider__background-lower{background:rgb(0,150,136)}.mdl-slider__background-lower,.mdl-slider__background-upper{-webkit-flex:0;-ms-flex:0;flex:0;position:relative;border:0;padding:0}.mdl-slider__background-upper{background:rgba(0,0,0,.26);transition:left .18s cubic-bezier(.4,0,.2,1)}.mdl-snackbar{position:fixed;bottom:0;left:50%;cursor:default;background-color:#323232;z-index:3;display:block;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:"Roboto","Helvetica","Arial",sans-serif;will-change:transform;-webkit-transform:translate(0,80px);transform:translate(0,80px);transition:transform .25s cubic-bezier(.4,0,1,1);transition:transform .25s cubic-bezier(.4,0,1,1),-webkit-transform .25s cubic-bezier(.4,0,1,1);pointer-events:none}@media (max-width:479px){.mdl-snackbar{width:100%;left:0;min-height:48px;max-height:80px}}@media (min-width:480px){.mdl-snackbar{min-width:288px;max-width:568px;border-radius:2px;-webkit-transform:translate(-50%,80px);transform:translate(-50%,80px)}}.mdl-snackbar--active{-webkit-transform:translate(0,0);transform:translate(0,0);pointer-events:auto;transition:transform .25s cubic-bezier(0,0,.2,1);transition:transform .25s cubic-bezier(0,0,.2,1),-webkit-transform .25s cubic-bezier(0,0,.2,1)}@media (min-width:480px){.mdl-snackbar--active{-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}}.mdl-snackbar__text{padding:14px 12px 14px 24px;vertical-align:middle;color:#fff;float:left}.mdl-snackbar__action{background:0 0;border:none;color:rgb(68,138,255);float:right;padding:14px 24px 14px 12px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;line-height:1;letter-spacing:0;overflow:hidden;outline:none;opacity:0;pointer-events:none;cursor:pointer;text-decoration:none;text-align:center;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-snackbar__action::-moz-focus-inner{border:0}.mdl-snackbar__action:not([aria-hidden]){opacity:1;pointer-events:auto}.mdl-spinner{display:inline-block;position:relative;width:28px;height:28px}.mdl-spinner:not(.is-upgraded).is-active:after{content:"Loading..."}.mdl-spinner.is-upgraded.is-active{-webkit-animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite;animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite}@-webkit-keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.mdl-spinner__layer{position:absolute;width:100%;height:100%;opacity:0}.mdl-spinner__layer-1{border-color:#42a5f5}.mdl-spinner--single-color .mdl-spinner__layer-1{border-color:rgb(0,150,136)}.mdl-spinner.is-active .mdl-spinner__layer-1{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-2{border-color:#f44336}.mdl-spinner--single-color .mdl-spinner__layer-2{border-color:rgb(0,150,136)}.mdl-spinner.is-active .mdl-spinner__layer-2{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-3{border-color:#fdd835}.mdl-spinner--single-color .mdl-spinner__layer-3{border-color:rgb(0,150,136)}.mdl-spinner.is-active .mdl-spinner__layer-3{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-4{border-color:#4caf50}.mdl-spinner--single-color .mdl-spinner__layer-4{border-color:rgb(0,150,136)}.mdl-spinner.is-active .mdl-spinner__layer-4{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@-webkit-keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}@keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}.mdl-spinner__gap-patch{position:absolute;box-sizing:border-box;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__gap-patch .mdl-spinner__circle{width:1000%;left:-450%}.mdl-spinner__circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__circle-clipper .mdl-spinner__circle{width:200%}.mdl-spinner__circle{box-sizing:border-box;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent!important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0;left:0}.mdl-spinner__left .mdl-spinner__circle{border-right-color:transparent!important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.mdl-spinner.is-active .mdl-spinner__left .mdl-spinner__circle{-webkit-animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__right .mdl-spinner__circle{left:-100%;border-left-color:transparent!important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.mdl-spinner.is-active .mdl-spinner__right .mdl-spinner__circle{-webkit-animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}@keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}.mdl-switch{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0;overflow:visible;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-switch.is-upgraded{padding-left:28px}.mdl-switch__input{line-height:24px}.mdl-switch.is-upgraded .mdl-switch__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-switch__track{background:rgba(0,0,0,.26);position:absolute;left:0;top:5px;height:14px;width:36px;border-radius:14px;cursor:pointer}.mdl-switch.is-checked .mdl-switch__track{background:rgba(0,150,136,.5)}.mdl-switch__track fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__track{background:rgba(0,0,0,.12);cursor:auto}.mdl-switch__thumb{background:#fafafa;position:absolute;left:0;top:2px;height:20px;width:20px;border-radius:50%;cursor:pointer;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);transition-duration:.28s;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-property:left}.mdl-switch.is-checked .mdl-switch__thumb{background:rgb(0,150,136);left:16px;box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-switch__thumb fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__thumb{background:#bdbdbd;cursor:auto}.mdl-switch__focus-helper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-4px,-4px);transform:translate(-4px,-4px);display:inline-block;box-sizing:border-box;width:8px;height:8px;border-radius:50%;background-color:transparent}.mdl-switch.is-focused .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-switch.is-focused.is-checked .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(0,150,136,.26);background-color:rgba(0,150,136,.26)}.mdl-switch__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0;left:24px}.mdl-switch__label fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__label{color:#bdbdbd;cursor:auto}.mdl-switch__ripple-container{position:absolute;z-index:2;top:-12px;left:-14px;box-sizing:border-box;width:48px;height:48px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000);transition-duration:.4s;transition-timing-function:step-end;transition-property:left}.mdl-switch__ripple-container .mdl-ripple{background:rgb(0,150,136)}.mdl-switch__ripple-container fieldset[disabled] .mdl-switch,.mdl-switch.is-disabled .mdl-switch__ripple-container{cursor:auto}fieldset[disabled] .mdl-switch .mdl-switch__ripple-container .mdl-ripple,.mdl-switch.is-disabled .mdl-switch__ripple-container .mdl-ripple{background:0 0}.mdl-switch.is-checked .mdl-switch__ripple-container{left:2px}.mdl-tabs{display:block;width:100%}.mdl-tabs__tab-bar{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:space-between;-ms-flex-line-pack:justify;align-content:space-between;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;height:48px;padding:0;margin:0;border-bottom:1px solid #e0e0e0}.mdl-tabs__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(0,0,0,.54);overflow:hidden}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active{color:rgba(0,0,0,.87)}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active:after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(0,150,136);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-tabs__tab .mdl-tabs__ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-tabs__tab .mdl-tabs__ripple-container .mdl-ripple{background:rgb(0,150,136)}.mdl-tabs__panel{display:block}.mdl-tabs.is-upgraded .mdl-tabs__panel{display:none}.mdl-tabs.is-upgraded .mdl-tabs__panel.is-active{display:block}@-webkit-keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}@keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}.mdl-textfield{position:relative;font-size:16px;display:inline-block;box-sizing:border-box;width:300px;max-width:100%;margin:0;padding:20px 0}.mdl-textfield .mdl-button{position:absolute;bottom:20px}.mdl-textfield--align-right{text-align:right}.mdl-textfield--full-width{width:100%}.mdl-textfield--expandable{min-width:32px;width:auto;min-height:32px}.mdl-textfield--expandable .mdl-button--icon{top:16px}.mdl-textfield__input{border:none;border-bottom:1px solid rgba(0,0,0,.12);display:block;font-size:16px;font-family:"Helvetica","Arial",sans-serif;margin:0;padding:4px 0;width:100%;background:0 0;text-align:left;color:inherit}.mdl-textfield__input[type="number"]{-moz-appearance:textfield}.mdl-textfield__input[type="number"]::-webkit-inner-spin-button,.mdl-textfield__input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.mdl-textfield.is-focused .mdl-textfield__input{outline:none}.mdl-textfield.is-invalid .mdl-textfield__input{border-color:#d50000;box-shadow:none}fieldset[disabled] .mdl-textfield .mdl-textfield__input,.mdl-textfield.is-disabled .mdl-textfield__input{background-color:transparent;border-bottom:1px dotted rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-textfield textarea.mdl-textfield__input{display:block}.mdl-textfield__label{bottom:0;color:rgba(0,0,0,.26);font-size:16px;left:0;right:0;pointer-events:none;position:absolute;display:block;top:24px;width:100%;overflow:hidden;white-space:nowrap;text-align:left}.mdl-textfield.is-dirty .mdl-textfield__label,.mdl-textfield.has-placeholder .mdl-textfield__label{visibility:hidden}.mdl-textfield--floating-label .mdl-textfield__label{transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{transition:none}fieldset[disabled] .mdl-textfield .mdl-textfield__label,.mdl-textfield.is-disabled.is-disabled .mdl-textfield__label{color:rgba(0,0,0,.26)}.mdl-textfield--floating-label.is-focused .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__label{color:rgb(0,150,136);font-size:12px;top:4px;visibility:visible}.mdl-textfield--floating-label.is-focused .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.has-placeholder .mdl-textfield__expandable-holder .mdl-textfield__label{top:-16px}.mdl-textfield--floating-label.is-invalid .mdl-textfield__label{color:#d50000;font-size:12px}.mdl-textfield__label:after{background-color:rgb(0,150,136);bottom:20px;content:'';height:2px;left:45%;position:absolute;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);visibility:hidden;width:10px}.mdl-textfield.is-focused .mdl-textfield__label:after{left:0;visibility:visible;width:100%}.mdl-textfield.is-invalid .mdl-textfield__label:after{background-color:#d50000}.mdl-textfield__error{color:#d50000;position:absolute;font-size:12px;margin-top:3px;visibility:hidden;display:block}.mdl-textfield.is-invalid .mdl-textfield__error{visibility:visible}.mdl-textfield__expandable-holder{display:inline-block;position:relative;margin-left:32px;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-block;max-width:.1px}.mdl-textfield.is-focused .mdl-textfield__expandable-holder,.mdl-textfield.is-dirty .mdl-textfield__expandable-holder{max-width:600px}.mdl-textfield__expandable-holder .mdl-textfield__label:after{bottom:0}.mdl-tooltip{-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:top center;transform-origin:top center;z-index:999;background:rgba(97,97,97,.9);border-radius:2px;color:#fff;display:inline-block;font-size:10px;font-weight:500;line-height:14px;max-width:170px;position:fixed;top:-500px;left:-500px;padding:8px;text-align:center}.mdl-tooltip.is-active{-webkit-animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards;animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards}.mdl-tooltip--large{line-height:14px;font-size:14px;padding:16px}@-webkit-keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}@keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}.mdl-shadow--2dp{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-shadow--3dp{box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-shadow--4dp{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2)}.mdl-shadow--6dp{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.2)}.mdl-shadow--8dp{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.2)}.mdl-shadow--16dp{box-shadow:0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)}.mdl-shadow--24dp{box-shadow:0 9px 46px 8px rgba(0,0,0,.14),0 11px 15px -7px rgba(0,0,0,.12),0 24px 38px 3px rgba(0,0,0,.2)}.mdl-grid{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;margin:0 auto;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.mdl-grid.mdl-grid--no-spacing{padding:0}.mdl-cell{box-sizing:border-box}.mdl-cell--top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.mdl-cell--middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-cell--bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.mdl-cell--stretch{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.mdl-grid.mdl-grid--no-spacing>.mdl-cell{margin:0}.mdl-cell--order-1{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12{-webkit-order:12;-ms-flex-order:12;order:12}@media (max-width:479px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:100%}.mdl-cell--hide-phone{display:none!important}.mdl-cell--order-1-phone.mdl-cell--order-1-phone{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-phone.mdl-cell--order-2-phone{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-phone.mdl-cell--order-3-phone{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-phone.mdl-cell--order-4-phone{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-phone.mdl-cell--order-5-phone{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-phone.mdl-cell--order-6-phone{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-phone.mdl-cell--order-7-phone{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-phone.mdl-cell--order-8-phone{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-phone.mdl-cell--order-9-phone{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-phone.mdl-cell--order-10-phone{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-phone.mdl-cell--order-11-phone{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-phone.mdl-cell--order-12-phone{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:25%}.mdl-cell--2-col,.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:50%}.mdl-cell--3-col,.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:75%}.mdl-cell--4-col,.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:100%}.mdl-cell--5-col,.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:100%}.mdl-cell--6-col,.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:100%}.mdl-cell--7-col,.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:100%}.mdl-cell--8-col,.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-phone.mdl-cell--1-offset-phone{margin-left:25%}.mdl-cell--2-offset,.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-phone.mdl-cell--2-offset-phone{margin-left:50%}.mdl-cell--3-offset,.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-phone.mdl-cell--3-offset-phone{margin-left:75%}}@media (min-width:480px) and (max-width:839px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:50%}.mdl-cell--hide-tablet{display:none!important}.mdl-cell--order-1-tablet.mdl-cell--order-1-tablet{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-tablet.mdl-cell--order-2-tablet{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-tablet.mdl-cell--order-3-tablet{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-tablet.mdl-cell--order-4-tablet{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-tablet.mdl-cell--order-5-tablet{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-tablet.mdl-cell--order-6-tablet{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-tablet.mdl-cell--order-7-tablet{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-tablet.mdl-cell--order-8-tablet{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-tablet.mdl-cell--order-9-tablet{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-tablet.mdl-cell--order-10-tablet{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-tablet.mdl-cell--order-11-tablet{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-tablet.mdl-cell--order-12-tablet{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:calc(12.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:12.5%}.mdl-cell--2-col,.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:25%}.mdl-cell--3-col,.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:calc(37.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:37.5%}.mdl-cell--4-col,.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:50%}.mdl-cell--5-col,.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:calc(62.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:62.5%}.mdl-cell--6-col,.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:75%}.mdl-cell--7-col,.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:calc(87.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:87.5%}.mdl-cell--8-col,.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:calc(12.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-tablet.mdl-cell--1-offset-tablet{margin-left:12.5%}.mdl-cell--2-offset,.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-tablet.mdl-cell--2-offset-tablet{margin-left:25%}.mdl-cell--3-offset,.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:calc(37.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-tablet.mdl-cell--3-offset-tablet{margin-left:37.5%}.mdl-cell--4-offset,.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-tablet.mdl-cell--4-offset-tablet{margin-left:50%}.mdl-cell--5-offset,.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:calc(62.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-tablet.mdl-cell--5-offset-tablet{margin-left:62.5%}.mdl-cell--6-offset,.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-tablet.mdl-cell--6-offset-tablet{margin-left:75%}.mdl-cell--7-offset,.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:calc(87.5% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-tablet.mdl-cell--7-offset-tablet{margin-left:87.5%}}@media (min-width:840px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:33.3333333333%}.mdl-cell--hide-desktop{display:none!important}.mdl-cell--order-1-desktop.mdl-cell--order-1-desktop{-webkit-order:1;-ms-flex-order:1;order:1}.mdl-cell--order-2-desktop.mdl-cell--order-2-desktop{-webkit-order:2;-ms-flex-order:2;order:2}.mdl-cell--order-3-desktop.mdl-cell--order-3-desktop{-webkit-order:3;-ms-flex-order:3;order:3}.mdl-cell--order-4-desktop.mdl-cell--order-4-desktop{-webkit-order:4;-ms-flex-order:4;order:4}.mdl-cell--order-5-desktop.mdl-cell--order-5-desktop{-webkit-order:5;-ms-flex-order:5;order:5}.mdl-cell--order-6-desktop.mdl-cell--order-6-desktop{-webkit-order:6;-ms-flex-order:6;order:6}.mdl-cell--order-7-desktop.mdl-cell--order-7-desktop{-webkit-order:7;-ms-flex-order:7;order:7}.mdl-cell--order-8-desktop.mdl-cell--order-8-desktop{-webkit-order:8;-ms-flex-order:8;order:8}.mdl-cell--order-9-desktop.mdl-cell--order-9-desktop{-webkit-order:9;-ms-flex-order:9;order:9}.mdl-cell--order-10-desktop.mdl-cell--order-10-desktop{-webkit-order:10;-ms-flex-order:10;order:10}.mdl-cell--order-11-desktop.mdl-cell--order-11-desktop{-webkit-order:11;-ms-flex-order:11;order:11}.mdl-cell--order-12-desktop.mdl-cell--order-12-desktop{-webkit-order:12;-ms-flex-order:12;order:12}.mdl-cell--1-col,.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:calc(8.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:8.3333333333%}.mdl-cell--2-col,.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:calc(16.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:16.6666666667%}.mdl-cell--3-col,.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:25%}.mdl-cell--4-col,.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:33.3333333333%}.mdl-cell--5-col,.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:calc(41.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:41.6666666667%}.mdl-cell--6-col,.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:50%}.mdl-cell--7-col,.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:calc(58.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:58.3333333333%}.mdl-cell--8-col,.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:calc(66.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:66.6666666667%}.mdl-cell--9-col,.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:75%}.mdl-cell--10-col,.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:calc(83.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:83.3333333333%}.mdl-cell--11-col,.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:calc(91.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:91.6666666667%}.mdl-cell--12-col,.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:100%}.mdl-cell--1-offset,.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:calc(8.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--1-offset-desktop.mdl-cell--1-offset-desktop{margin-left:8.3333333333%}.mdl-cell--2-offset,.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:calc(16.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--2-offset-desktop.mdl-cell--2-offset-desktop{margin-left:16.6666666667%}.mdl-cell--3-offset,.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:calc(25% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--3-offset-desktop.mdl-cell--3-offset-desktop{margin-left:25%}.mdl-cell--4-offset,.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:calc(33.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--4-offset-desktop.mdl-cell--4-offset-desktop{margin-left:33.3333333333%}.mdl-cell--5-offset,.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:calc(41.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--5-offset-desktop.mdl-cell--5-offset-desktop{margin-left:41.6666666667%}.mdl-cell--6-offset,.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:calc(50% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--6-offset-desktop.mdl-cell--6-offset-desktop{margin-left:50%}.mdl-cell--7-offset,.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:calc(58.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--7-offset-desktop.mdl-cell--7-offset-desktop{margin-left:58.3333333333%}.mdl-cell--8-offset,.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:calc(66.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--8-offset-desktop.mdl-cell--8-offset-desktop{margin-left:66.6666666667%}.mdl-cell--9-offset,.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:calc(75% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--9-offset-desktop.mdl-cell--9-offset-desktop{margin-left:75%}.mdl-cell--10-offset,.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:calc(83.3333333333% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--10-offset-desktop.mdl-cell--10-offset-desktop{margin-left:83.3333333333%}.mdl-cell--11-offset,.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:calc(91.6666666667% + 8px)}.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset,.mdl-grid.mdl-grid--no-spacing>.mdl-cell--11-offset-desktop.mdl-cell--11-offset-desktop{margin-left:91.6666666667%}}body{margin:0}.styleguide-demo h1{margin:48px 24px 0}.styleguide-demo h1:after{content:'';display:block;width:100%;border-bottom:1px solid rgba(0,0,0,.5);margin-top:24px}.styleguide-demo{opacity:0;transition:opacity .6s ease}.styleguide-masthead{height:256px;background:#212121;padding:115px 16px 0}.styleguide-container{position:relative;max-width:960px;width:100%}.styleguide-title{color:#fff;bottom:auto;position:relative;font-size:56px;font-weight:300;line-height:1;letter-spacing:-.02em}.styleguide-title:after{border-bottom:0}.styleguide-title span{font-weight:300}.mdl-styleguide .mdl-layout__drawer .mdl-navigation__link{padding:10px 24px}.demosLoaded .styleguide-demo{opacity:1}iframe{display:block;width:100%;border:none}iframe.heightSet{overflow:hidden}.demo-wrapper{margin:24px}.demo-wrapper iframe{border:1px solid rgba(0,0,0,.5)} \ No newline at end of file diff --git a/modules/material/www/mfa-backupcode.svg b/modules/material/www/mfa-backupcode.svg new file mode 100644 index 00000000..4a23bd31 --- /dev/null +++ b/modules/material/www/mfa-backupcode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/material/www/mfa-manager.svg b/modules/material/www/mfa-manager.svg new file mode 100644 index 00000000..a4cd8c07 --- /dev/null +++ b/modules/material/www/mfa-manager.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/material/www/mfa-totp.svg b/modules/material/www/mfa-totp.svg new file mode 100644 index 00000000..1af9af86 --- /dev/null +++ b/modules/material/www/mfa-totp.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/modules/material/www/mfa-u2f-api.js b/modules/material/www/mfa-u2f-api.js new file mode 100644 index 00000000..a0518ef0 --- /dev/null +++ b/modules/material/www/mfa-u2f-api.js @@ -0,0 +1,748 @@ +//Copyright 2014-2015 Google Inc. All rights reserved. + +//Use of this source code is governed by a BSD-style +//license that can be found in the LICENSE file or at +//https://developers.google.com/open-source/licenses/bsd + +/** + * @fileoverview The U2F api. + */ +'use strict'; + + +/** + * Namespace for the U2F api. + * @type {Object} + */ +var u2f = u2f || {}; + +/** + * FIDO U2F Javascript API Version + * @number + */ +var js_api_version; + +/** + * The U2F extension id + * @const {string} + */ +// The Chrome packaged app extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the package Chrome app and does not require installing the U2F Chrome extension. +u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; +// The U2F Chrome extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the U2F Chrome extension to authenticate. +// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; + + +/** + * Message types for messsages to/from the extension + * @const + * @enum {string} + */ +u2f.MessageTypes = { + 'U2F_REGISTER_REQUEST': 'u2f_register_request', + 'U2F_REGISTER_RESPONSE': 'u2f_register_response', + 'U2F_SIGN_REQUEST': 'u2f_sign_request', + 'U2F_SIGN_RESPONSE': 'u2f_sign_response', + 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', + 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' +}; + + +/** + * Response status codes + * @const + * @enum {number} + */ +u2f.ErrorCodes = { + 'OK': 0, + 'OTHER_ERROR': 1, + 'BAD_REQUEST': 2, + 'CONFIGURATION_UNSUPPORTED': 3, + 'DEVICE_INELIGIBLE': 4, + 'TIMEOUT': 5 +}; + + +/** + * A message for registration requests + * @typedef {{ + * type: u2f.MessageTypes, + * appId: ?string, + * timeoutSeconds: ?number, + * requestId: ?number + * }} + */ +u2f.U2fRequest; + + +/** + * A message for registration responses + * @typedef {{ + * type: u2f.MessageTypes, + * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), + * requestId: ?number + * }} + */ +u2f.U2fResponse; + + +/** + * An error object for responses + * @typedef {{ + * errorCode: u2f.ErrorCodes, + * errorMessage: ?string + * }} + */ +u2f.Error; + +/** + * Data object for a single sign request. + * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} + */ +u2f.Transport; + + +/** + * Data object for a single sign request. + * @typedef {Array} + */ +u2f.Transports; + +/** + * Data object for a single sign request. + * @typedef {{ + * version: string, + * challenge: string, + * keyHandle: string, + * appId: string + * }} + */ +u2f.SignRequest; + + +/** + * Data object for a sign response. + * @typedef {{ + * keyHandle: string, + * signatureData: string, + * clientData: string + * }} + */ +u2f.SignResponse; + + +/** + * Data object for a registration request. + * @typedef {{ + * version: string, + * challenge: string + * }} + */ +u2f.RegisterRequest; + + +/** + * Data object for a registration response. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: Transports, + * appId: string + * }} + */ +u2f.RegisterResponse; + + +/** + * Data object for a registered key. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: ?Transports, + * appId: ?string + * }} + */ +u2f.RegisteredKey; + + +/** + * Data object for a get API register response. + * @typedef {{ + * js_api_version: number + * }} + */ +u2f.GetJsApiVersionResponse; + + +//Low level MessagePort API support + +/** + * Sets up a MessagePort to the U2F extension using the + * available mechanisms. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + */ +u2f.getMessagePort = function(callback) { + if (typeof chrome != 'undefined' && chrome.runtime) { + // The actual message here does not matter, but we need to get a reply + // for the callback to run. Thus, send an empty signature request + // in order to get a failure response. + var msg = { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: [] + }; + chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { + if (!chrome.runtime.lastError) { + // We are on a whitelisted origin and can talk directly + // with the extension. + u2f.getChromeRuntimePort_(callback); + } else { + // chrome.runtime was available, but we couldn't message + // the extension directly, use iframe + u2f.getIframePort_(callback); + } + }); + } else if (u2f.isAndroidChrome_()) { + u2f.getAuthenticatorPort_(callback); + } else if (u2f.isIosChrome_()) { + u2f.getIosPort_(callback); + } else { + // chrome.runtime was not available at all, which is normal + // when this origin doesn't have access to any extensions. + u2f.getIframePort_(callback); + } +}; + +/** + * Detect chrome running on android based on the browser's useragent. + * @private + */ +u2f.isAndroidChrome_ = function() { + var userAgent = navigator.userAgent; + return userAgent.indexOf('Chrome') != -1 && + userAgent.indexOf('Android') != -1; +}; + +/** + * Detect chrome running on iOS based on the browser's platform. + * @private + */ +u2f.isIosChrome_ = function() { + return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; +}; + +/** + * Connects directly to the extension via chrome.runtime.connect. + * @param {function(u2f.WrappedChromeRuntimePort_)} callback + * @private + */ +u2f.getChromeRuntimePort_ = function(callback) { + var port = chrome.runtime.connect(u2f.EXTENSION_ID, + {'includeTlsChannelId': true}); + setTimeout(function() { + callback(new u2f.WrappedChromeRuntimePort_(port)); + }, 0); +}; + +/** + * Return a 'port' abstraction to the Authenticator app. + * @param {function(u2f.WrappedAuthenticatorPort_)} callback + * @private + */ +u2f.getAuthenticatorPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedAuthenticatorPort_()); + }, 0); +}; + +/** + * Return a 'port' abstraction to the iOS client app. + * @param {function(u2f.WrappedIosPort_)} callback + * @private + */ +u2f.getIosPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedIosPort_()); + }, 0); +}; + +/** + * A wrapper for chrome.runtime.Port that is compatible with MessagePort. + * @param {Port} port + * @constructor + * @private + */ +u2f.WrappedChromeRuntimePort_ = function(port) { + this.port_ = port; +}; + +/** + * Format and return a sign request compliant with the JS API version supported by the extension. + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatSignRequest_ = + function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: challenge, + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: signRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + appId: appId, + challenge: challenge, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + }; + +/** + * Format and return a register request compliant with the JS API version supported by the extension.. + * @param {Array} signRequests + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatRegisterRequest_ = + function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + for (var i = 0; i < registerRequests.length; i++) { + registerRequests[i].appId = appId; + } + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: registerRequests[0], + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + signRequests: signRequests, + registerRequests: registerRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + appId: appId, + registerRequests: registerRequests, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + }; + + +/** + * Posts a message on the underlying channel. + * @param {Object} message + */ +u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { + this.port_.postMessage(message); +}; + + +/** + * Emulates the HTML 5 addEventListener interface. Works only for the + * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedChromeRuntimePort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message' || name == 'onmessage') { + this.port_.onMessage.addListener(function(message) { + // Emulate a minimal MessageEvent object + handler({'data': message}); + }); + } else { + console.error('WrappedChromeRuntimePort only supports onMessage'); + } + }; + +/** + * Wrap the Authenticator app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedAuthenticatorPort_ = function() { + this.requestId_ = -1; + this.requestObject_ = null; +} + +/** + * Launch the Authenticator intent. + * @param {Object} message + */ +u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + + ';end'; + document.location = intentUrl; +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { + return "WrappedAuthenticatorPort_"; +}; + + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message') { + var self = this; + /* Register a callback to that executes when + * chrome injects the response. */ + window.addEventListener( + 'message', self.onRequestUpdate_.bind(self, handler), false); + } else { + console.error('WrappedAuthenticatorPort only supports message'); + } +}; + +/** + * Callback invoked when a response is received from the Authenticator. + * @param function({data: Object}) callback + * @param {Object} message message Object + */ +u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = + function(callback, message) { + var messageObject = JSON.parse(message.data); + var intentUrl = messageObject['intentURL']; + + var errorCode = messageObject['errorCode']; + var responseObject = null; + if (messageObject.hasOwnProperty('data')) { + responseObject = /** @type {Object} */ ( + JSON.parse(messageObject['data'])); + } + + callback({'data': responseObject}); + }; + +/** + * Base URL for intents to Authenticator. + * @const + * @private + */ +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; + +/** + * Wrap the iOS client app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedIosPort_ = function() {}; + +/** + * Launch the iOS client app request + * @param {Object} message + */ +u2f.WrappedIosPort_.prototype.postMessage = function(message) { + var str = JSON.stringify(message); + var url = "u2f://auth?" + encodeURI(str); + location.replace(url); +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedIosPort_.prototype.getPortType = function() { + return "WrappedIosPort_"; +}; + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name !== 'message') { + console.error('WrappedIosPort only supports message'); + } +}; + +/** + * Sets up an embedded trampoline iframe, sourced from the extension. + * @param {function(MessagePort)} callback + * @private + */ +u2f.getIframePort_ = function(callback) { + // Create the iframe + var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; + var iframe = document.createElement('iframe'); + iframe.src = iframeOrigin + '/u2f-comms.html'; + iframe.setAttribute('style', 'display:none'); + document.body.appendChild(iframe); + + var channel = new MessageChannel(); + var ready = function(message) { + if (message.data == 'ready') { + channel.port1.removeEventListener('message', ready); + callback(channel.port1); + } else { + console.error('First event on iframe port was not "ready"'); + } + }; + channel.port1.addEventListener('message', ready); + channel.port1.start(); + + iframe.addEventListener('load', function() { + // Deliver the port to the iframe and initialize + iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); + }); +}; + + +//High-level JS API + +/** + * Default extension response timeout in seconds. + * @const + */ +u2f.EXTENSION_TIMEOUT_SEC = 30; + +/** + * A singleton instance for a MessagePort to the extension. + * @type {MessagePort|u2f.WrappedChromeRuntimePort_} + * @private + */ +u2f.port_ = null; + +/** + * Callbacks waiting for a port + * @type {Array} + * @private + */ +u2f.waitingForPort_ = []; + +/** + * A counter for requestIds. + * @type {number} + * @private + */ +u2f.reqCounter_ = 0; + +/** + * A map from requestIds to client callbacks + * @type {Object.} + * @private + */ +u2f.callbackMap_ = {}; + +/** + * Creates or retrieves the MessagePort singleton to use. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + * @private + */ +u2f.getPortSingleton_ = function(callback) { + if (u2f.port_) { + callback(u2f.port_); + } else { + if (u2f.waitingForPort_.length == 0) { + u2f.getMessagePort(function(port) { + u2f.port_ = port; + u2f.port_.addEventListener('message', + /** @type {function(Event)} */ (u2f.responseHandler_)); + + // Careful, here be async callbacks. Maybe. + while (u2f.waitingForPort_.length) + u2f.waitingForPort_.shift()(u2f.port_); + }); + } + u2f.waitingForPort_.push(callback); + } +}; + +/** + * Handles response messages from the extension. + * @param {MessageEvent.} message + * @private + */ +u2f.responseHandler_ = function(message) { + var response = message.data; + var reqId = response['requestId']; + if (!reqId || !u2f.callbackMap_[reqId]) { + console.error('Unknown or missing requestId in response.'); + return; + } + var cb = u2f.callbackMap_[reqId]; + delete u2f.callbackMap_[reqId]; + cb(response['responseData']); +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the sign request. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual sign request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual sign request in the supported API version. + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the register request. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual register request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual register request in the supported API version. + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatRegisterRequest_( + appId, registeredKeys, registerRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + + +/** + * Dispatches a message to the extension to find out the supported + * JS API version. + * If the user is on a mobile phone and is thus using Google Authenticator instead + * of the Chrome extension, don't send the request and simply return 0. + * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.getApiVersion = function(callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + // If we are using Android Google Authenticator or iOS client app, + // do not fire an intent to ask which JS API version to use. + if (port.getPortType) { + var apiVersion; + switch (port.getPortType()) { + case 'WrappedIosPort_': + case 'WrappedAuthenticatorPort_': + apiVersion = 1.1; + break; + + default: + apiVersion = 0; + break; + } + callback({ 'js_api_version': apiVersion }); + return; + } + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var req = { + type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, + timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), + requestId: reqId + }; + port.postMessage(req); + }); +}; diff --git a/modules/material/www/mfa-u2f.svg b/modules/material/www/mfa-u2f.svg new file mode 100644 index 00000000..3f1a677f --- /dev/null +++ b/modules/material/www/mfa-u2f.svg @@ -0,0 +1,27 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + + diff --git a/modules/material/www/mfa-webauthn.svg b/modules/material/www/mfa-webauthn.svg new file mode 100644 index 00000000..3f1a677f --- /dev/null +++ b/modules/material/www/mfa-webauthn.svg @@ -0,0 +1,27 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + + diff --git a/modules/material/www/shield.svg b/modules/material/www/shield.svg new file mode 100644 index 00000000..1de5a003 --- /dev/null +++ b/modules/material/www/shield.svg @@ -0,0 +1,3 @@ + + + diff --git a/modules/material/www/simplewebauthn/LICENSE.md b/modules/material/www/simplewebauthn/LICENSE.md new file mode 100644 index 00000000..70730ac2 --- /dev/null +++ b/modules/material/www/simplewebauthn/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Matthew Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/material/www/simplewebauthn/browser.js b/modules/material/www/simplewebauthn/browser.js new file mode 100644 index 00000000..8b1de96e --- /dev/null +++ b/modules/material/www/simplewebauthn/browser.js @@ -0,0 +1,2 @@ +/* [@simplewebauthn/browser] Version: 4.1.0 - Wednesday, September 1st, 2021, 9:11:50 AM */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).SimpleWebAuthnBrowser={})}(this,(function(e){"use strict";function t(e){const t=new Uint8Array(e);let n="";for(const e of t)n+=String.fromCharCode(e);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function n(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),n=(4-t.length%4)%4,r=t.padEnd(t.length+n,"="),o=atob(r),i=new ArrayBuffer(o.length),a=new Uint8Array(i);for(let e=0;e i { + margin: 0 1em; +} + +i.material-icons.mdl-typography--display-4 { + font-size: 112px; /* needed to override font-size established in material icons. */ +} + +.margin { + margin: 1em; +} + +.white-bg { + background-color: white; +} + +/* file entire card, so need to match mdl-card's min-height of 200px */ +.fixed-height { + height: 200px; +} + +.fill-parent { + width: 100%; + height: 100%; +} + +.scale-to-parent { + max-width: 90%; + max-height: 90%; +} + +.hide { + visibility: hidden; +} + +.show { + visibility: visible; +} + +/* out-of-box styles for the cards don't work well when there are many cards */ +.mdl-card { + width: initial; + margin: 1em; +} + +/* cards on profile review needed to have consistent width */ +.mdl-card.fixed-width { + width: 365px; /* takes into acocunt "... (10 remaining)" for appropriate width */ +} + +/* don't want images to be too small on cards */ +.mdl-card__media > img { + min-width: 20%; +} + +/* phones (2 cards / row) */ +@media only screen and (min-width : 320px) { + .mdl-card.row-aware { + min-width: calc(50% - 2em); + max-width: calc(50% - 2em); + } + + .mdl-card.fill-phone-viewport { + height: 100vh; + width: 100vw; + } +} +/* phones, small tablets, landscape */ +@media only screen and (min-width : 600px) { + /* (3 cards / row) */ + .mdl-card.row-aware { + min-width: calc(33% - 2em); + max-width: calc(33% - 2em); + } + + .mdl-card.fill-phone-viewport { + height: auto; + width: auto; + } +} +/* tablets, small desktops */ +@media only screen and (min-width : 850px) { + /* (4 cards / row) */ + .mdl-card.row-aware { + min-width: calc(25% - 2em); + max-width: calc(25% - 2em); + } + + .mdl-card.fill-phone-viewport { + height: auto; + width: auto; + } +} +/* desktops */ +@media only screen and (min-width : 1024px) { + /* (5 cards / row) */ + .mdl-card.row-aware { + min-width: calc(20% - 2em); + max-width: calc(20% - 2em); + } + + .mdl-card.fill-phone-viewport { + height: auto; + width: auto; + } +} + +.mdl-card.disabled, .mdl-button[disabled].not-allowed, a[href=''][download].mdl-button--disabled.not-allowed { + cursor: not-allowed; +} + +.mdl-card.disabled img { + opacity: 0.3; +} + +.alert { + max-width: 80%; + min-width: 30%; + background-color: tomato; + border-radius: 0.33em; + padding: 1em; + + /* had to center manually instead of using flex due to ie11 bug that + was causing idp cards not to wrap when parent container was centered + */ + margin-left: auto; + margin-right: auto; +} + +.alert a { + color: blue; +} + +/* The font-size in the mdl-textfield was overriding the one in caption since + it was defined later in the CSS but the font-size from caption is what was + needed here so more specificity required to override it back */ +.mdl-textfield.mdl-typography--caption { + font-size: 12px +} + + +/* special case where we want a button for all it's built-in characteristics, +e.g., primary color, but also want to set the text apart a bit. */ +.mdl-button.mdl-typography--caption { + text-transform: none; + font-size: 12px +} +a.mdl-button.mdl-typography--body-2, a.mdl-button.mdl-typography--body-2 > i.material-icons { + text-transform: none; + font-size: 14px +} + +/* didn't feel like the padding was enough out of the box */ +.mdl-card__actions { + padding: 1em; +} + +/* needed ability to center text in these card titles at times */ +.mdl-card__title.center { + justify-content: center; +} + +[flex] { + flex: 1; +} + +.gradient-bg { + background: linear-gradient(rgba(200,200,200,1) 0%, + rgba(250,250,250,1) 50%, + rgba(200,200,200,1) 100%); +} + +.mdl-card > .mdl-card__media > img.icon { + max-width: 24px; +} + +/* Material icons */ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: local('Material Icons'), + local('MaterialIcons-Regular'), + url(material-icons.woff2) format('woff2'), + url(material-icons.woff) format('woff'); +} diff --git a/modules/mfa/lib/Assert.php b/modules/mfa/lib/Assert.php new file mode 100644 index 00000000..7d0ef245 --- /dev/null +++ b/modules/mfa/lib/Assert.php @@ -0,0 +1,57 @@ +initComposerAutoloader(); + assert('is_array($config)'); + + $this->loggerClass = $config['loggerClass'] ?? Psr3SamlLogger::class; + $this->logger = LoggerFactory::get($this->loggerClass); + + $this->loadValuesFromConfig($config, [ + 'mfaSetupUrl', + 'employeeIdAttr', + 'idBrokerAccessToken', + 'idBrokerBaseUri', + 'idpDomainName', + ]); + + $tempTrustedIpRanges = $config['idBrokerTrustedIpRanges'] ?? ''; + if (! empty($tempTrustedIpRanges)) { + $this->idBrokerTrustedIpRanges = explode(',', $tempTrustedIpRanges); + } + $this->idBrokerAssertValidIp = (bool)($config['idBrokerAssertValidIp'] ?? true); + $this->idBrokerClientClass = $config['idBrokerClientClass'] ?? IdBrokerClient::class; + } + + protected function loadValuesFromConfig($config, $attributes) + { + foreach ($attributes as $attribute) { + $this->$attribute = $config[$attribute] ?? null; + + self::validateConfigValue( + $attribute, + $this->$attribute, + $this->logger + ); + } + } + + /** + * Validate the given config value + * + * @param string $attribute The name of the attribute. + * @param mixed $value The value to check. + * @param LoggerInterface $logger The logger. + * @throws \Exception + */ + public static function validateConfigValue($attribute, $value, $logger) + { + if (empty($value) || !is_string($value)) { + $exception = new \Exception(sprintf( + 'The value we have for %s (%s) is empty or is not a string', + $attribute, + var_export($value, true) + ), 1507146042); + + $logger->critical($exception->getMessage()); + throw $exception; + } + } + + /** + * Get the specified attribute from the given state data. + * + * NOTE: If the attribute's data is an array, the first value will be + * returned. Otherwise, the attribute's data will simply be returned + * as-is. + * + * @param string $attributeName The name of the attribute. + * @param array $state The state data. + * @return mixed The attribute value, or null if not found. + */ + protected function getAttribute($attributeName, $state) + { + $attributeData = $state['Attributes'][$attributeName] ?? null; + + if (is_array($attributeData)) { + return $attributeData[0] ?? null; + } + + return $attributeData; + } + + /** + * Get all of the values for the specified attribute from the given state + * data. + * + * NOTE: If the attribute's data is an array, it will be returned as-is. + * Otherwise, it will be returned as a single-entry array of the data. + * + * @param string $attributeName The name of the attribute. + * @param array $state The state data. + * @return array|null The attribute's value(s), or null if the attribute was + * not found. + */ + protected function getAttributeAllValues($attributeName, $state) + { + $attributeData = $state['Attributes'][$attributeName] ?? null; + + return is_null($attributeData) ? null : (array)$attributeData; + } + + /** + * Get an ID Broker client. + * + * @param array $idBrokerConfig + * @return IdBrokerClient + */ + protected static function getIdBrokerClient($idBrokerConfig) + { + $clientClass = $idBrokerConfig['clientClass']; + $baseUri = $idBrokerConfig['baseUri']; + $accessToken = $idBrokerConfig['accessToken']; + $trustedIpRanges = $idBrokerConfig['trustedIpRanges']; + $assertValidIp = $idBrokerConfig['assertValidIp']; + + return new $clientClass($baseUri, $accessToken, [ + 'http_client_options' => [ + 'timeout' => 10, + ], + IdBrokerClient::TRUSTED_IPS_CONFIG => $trustedIpRanges, + IdBrokerClient::ASSERT_VALID_BROKER_IP_CONFIG => $assertValidIp, + ]); + } + + /** + * Get the MFA type to use based on the available options. + * + * @param array[] $mfaOptions The available MFA options. + * @param int $mfaId The ID of the desired MFA option. + * @return array The MFA option to use. + * @throws \InvalidArgumentException + * @throws \Exception + */ + public static function getMfaOptionById($mfaOptions, $mfaId) + { + if (empty($mfaId)) { + throw new \Exception('No MFA ID was provided.'); + } + + foreach ($mfaOptions as $mfaOption) { + if ((int)$mfaOption['id'] === (int)$mfaId) { + return $mfaOption; + } + } + + throw new \Exception( + 'No MFA option has an ID of ' . var_export($mfaId, true) + ); + } + + /** + * Get the MFA type to use based on the available options. + * + * @param array[] $mfaOptions The available MFA options. + * @param string $userAgent The User-Agent sent by the user's browser, used + * for detecting WebAuthn support. + * @return array The MFA option to use. + * @throws \InvalidArgumentException + * @throws \Exception + */ + public static function getMfaOptionToUse($mfaOptions, $userAgent) + { + if (empty($mfaOptions)) { + throw new \Exception('No MFA options were provided.'); + } + + $recentMfa = self::getMostRecentUsedMfaOption($mfaOptions); + $mfaTypePriority = ['manager']; + + if (LoginBrowser::supportsWebAuthn($userAgent)) { + if (isset($recentMfa['type'])) { + $mfaTypePriority[] = $recentMfa['type']; + } + // Doubling up a type shouldn't be a problem. + array_push($mfaTypePriority, 'webauthn', 'totp', 'backupcode'); + } else { + // Browser doesn't support webauthn, so ensure that's the last option + if (isset($recentMfa['type']) && $recentMfa['type'] != 'webauthn') { + $mfaTypePriority[] = $recentMfa['type']; + } + array_push($mfaTypePriority, 'totp', 'backupcode', 'webauthn'); + } + + foreach ($mfaTypePriority as $mfaType) { + foreach ($mfaOptions as $mfaOption) { + if ($mfaOption['type'] === $mfaType) { + return $mfaOption; + } + } + } + + return $mfaOptions[0]; + } + + /** + * Get the MFA to use based on the one used most recently. + * + * @param array[] $mfaOptions The available MFA options. + * @return ?array The MFA option to use. + */ + private static function getMostRecentUsedMfaOption($mfaOptions) { + $recentMfa = null; + $recentDate = '1991-01-01T00:00:00Z'; + + foreach ($mfaOptions as $mfaOption) { + if (isset($mfaOption['last_used_utc']) && $mfaOption['last_used_utc'] > $recentDate) { + $recentMfa = $mfaOption; + $recentDate = $mfaOption['last_used_utc']; + } + } + return $recentMfa; + } + + /** + * Get the number of backup codes that the user had left PRIOR to this login. + * + * @param array $mfaOptions The list of MFA options. + * @return int The number of backup codes that the user HAD (prior to this + * login). + */ + public static function getNumBackupCodesUserHad(array $mfaOptions) + { + $numBackupCodes = 0; + foreach ($mfaOptions as $mfaOption) { + $mfaType = $mfaOption['type'] ?? null; + if ($mfaType === 'backupcode') { + $numBackupCodes += intval($mfaOption['data']['count'] ?? 0); + } + } + + return $numBackupCodes; + } + + /** + * Get the template identifier (string) to use for the specified MFA type. + * + * @param string $mfaType The desired MFA type, such as 'webauthn', 'totp', or 'backupcode'. + * @return string + * @throws \InvalidArgumentException + */ + public static function getTemplateFor($mfaType) + { + $mfaOptionTemplates = [ + 'backupcode' => 'mfa:prompt-for-mfa-backupcode.php', + 'totp' => 'mfa:prompt-for-mfa-totp.php', + 'webauthn' => 'mfa:prompt-for-mfa-webauthn.php', + 'manager' => 'mfa:prompt-for-mfa-manager.php', + ]; + $template = $mfaOptionTemplates[$mfaType] ?? null; + + if ($template === null) { + throw new \InvalidArgumentException(sprintf( + 'No %s MFA template is available.', + var_export($mfaType, true) + ), 1507219338); + } + return $template; + } + + /** + * Return the saml:RelayState if it begins with "http" or "https". Otherwise + * return an empty string. + * + * @param array $state + * @return string + */ + protected static function getRelayStateUrl($state) + { + if (array_key_exists('saml:RelayState', $state)) { + $samlRelayState = $state['saml:RelayState']; + + if (strpos($samlRelayState, "http://") === 0) { + return $samlRelayState; + } + + if (strpos($samlRelayState, "https://") === 0) { + return $samlRelayState; + } + } + return ''; + } + + /** + * Get new Printable Backup Codes for the user, then redirect the user to a + * page showing the user their new codes. + * + * NOTE: This function never returns. + * + * @param array $state The state data. + * @param LoggerInterface $logger A PSR-3 compatible logger. + */ + public static function giveUserNewBackupCodes(array &$state, $logger) + { + try { + $idBrokerClient = self::getIdBrokerClient($state['idBrokerConfig']); + $newMfaRecord = $idBrokerClient->mfaCreate( + $state['employeeId'], + 'backupcode' + ); + $newBackupCodes = $newMfaRecord['data']; + + $logger->warning(json_encode([ + 'event' => 'New backup codes result: succeeded', + 'employeeId' => $state['employeeId'], + ])); + } catch (\Throwable $t) { + $logger->error(json_encode([ + 'event' => 'New backup codes result: failed', + 'employeeId' => $state['employeeId'], + 'error' => $t->getCode() . ': ' . $t->getMessage(), + ])); + } + + self::updateStateWithNewMfaData($state, $logger); + + $state['newBackupCodes'] = $newBackupCodes ?? null; + $stateId = State::saveState($state, self::STAGE_SENT_TO_NEW_BACKUP_CODES_PAGE); + $url = Module::getModuleURL('mfa/new-backup-codes.php'); + + HTTP::redirectTrustedURL($url, ['StateId' => $stateId]); + } + + protected static function hasMfaOptions($mfa) + { + return (count($mfa['options']) > 0); + } + + /** + * See if the user has any MFA options other than the specified type. + * + * @param string $excludedMfaType + * @param array $state + * @return bool + */ + public static function hasMfaOptionsOtherThan($excludedMfaType, $state) + { + $mfaOptions = $state['mfaOptions'] ?? []; + foreach ($mfaOptions as $mfaOption) { + if (strval($mfaOption['type']) !== strval($excludedMfaType)) { + return true; + } + } + return false; + } + + protected function initComposerAutoloader() + { + $path = __DIR__ . '/../../../vendor/autoload.php'; + if (file_exists($path)) { + require_once $path; + } + } + + protected static function isHeadedToMfaSetupUrl($state, $mfaSetupUrl) + { + if (array_key_exists('saml:RelayState', $state)) { + $currentDestination = self::getRelayStateUrl($state); + if (! empty($currentDestination)) { + return (strpos($currentDestination, $mfaSetupUrl) === 0); + } + } + return false; + } + + /** + * Validate the given MFA submission. If successful, this function + * will NOT return. If the submission does not pass validation, an error + * message will be returned. + * + * @param int $mfaId The ID of the MFA option used. + * @param string $employeeId The Employee ID that this MFA option belongs to. + * @param string $mfaSubmission The value of the MFA submission. + * @param array $state The array of state information. + * @param bool $rememberMe Whether or not to set remember me cookies + * @param LoggerInterface $logger A PSR-3 compatible logger. + * @param string $mfaType The type of the MFA ('webauthn', 'totp', 'backupcode'). + * @param string $rpOrigin The Relying Party Origin (for WebAuthn) + * @return void|string If validation fails, an error message to show to the + * end user will be returned. + * @throws \Sil\PhpEnv\EnvVarNotFoundException + */ + public static function validateMfaSubmission( + $mfaId, + $employeeId, + $mfaSubmission, + $state, + $rememberMe, + LoggerInterface $logger, + string $mfaType, + string $rpOrigin + ) { + if (empty($mfaId)) { + return 'No MFA ID was provided.'; + } elseif (empty($employeeId)) { + return 'No Employee ID was provided.'; + } elseif (empty($mfaSubmission)) { + return 'No MFA submission was provided.'; + } elseif (empty($rpOrigin)) { + return 'No RP Origin was provided.'; + } + + try { + $idBrokerClient = self::getIdBrokerClient($state['idBrokerConfig']); + $mfaDataFromBroker = $idBrokerClient->mfaVerify( + $mfaId, + $employeeId, + $mfaSubmission, + $rpOrigin + ); + } catch (\Throwable $t) { + $message = 'Something went wrong while we were trying to do the ' + . '2-step verification.'; + if ($t instanceof ServiceException) { + if ($t->httpStatusCode === 400) { + if ($mfaType === 'backupcode') { + return 'Incorrect 2-step verification code. Printable backup ' + . 'codes can only be used once, please try a different code.'; + } + return 'Incorrect 2-step verification code.'; + } elseif ($t->httpStatusCode === 429){ + $logger->error(json_encode([ + 'event' => 'MFA is rate-limited', + 'employeeId' => $employeeId, + 'mfaId' => $mfaId, + 'mfaType' => $mfaType, + ])); + return 'There have been too many wrong answers recently. ' + . 'Please wait a minute, then try again.'; + } else { + $message .= ' (code ' . $t->httpStatusCode . ')'; + return $message; + } + } + + $logger->critical($t->getCode() . ': ' . $t->getMessage()); + return $message; + } + + self::updateStateWithNewMfaData($state, $logger); + + // Set remember me cookies if requested + if ($rememberMe) { + self::setRememberMeCookies($state['employeeId'], $state['mfaOptions']); + } + + $logger->warning(json_encode([ + 'event' => 'MFA validation result: success', + 'employeeId' => $employeeId, + 'mfaType' => $mfaType, + ])); + + // Handle situations where the user is running low on backup codes. + if ($mfaType === 'backupcode') { + $numBackupCodesUserHad = self::getNumBackupCodesUserHad( + $state['mfaOptions'] ?? [] + ); + $numBackupCodesRemaining = $numBackupCodesUserHad - 1; + + if ($numBackupCodesRemaining <= 0) { + self::redirectToOutOfBackupCodesMessage($state, $employeeId); + throw new \Exception('Failed to send user to out-of-backup-codes page.'); + } elseif ($numBackupCodesRemaining < 4) { + self::redirectToLowOnBackupCodesNag( + $state, + $employeeId, + $numBackupCodesRemaining + ); + throw new \Exception('Failed to send user to low-on-backup-codes page.'); + } + } + + /* + * If the user had to use a manager code, show the profile review page. + */ + if ($mfaType === 'manager' && isset($state['Attributes']['profile_review'])) { + $state['Attributes']['profile_review'] = 'yes'; + } + + unset($state['Attributes']['manager_email']); + + // The following function call will never return. + ProcessingChain::resumeProcessing($state); + throw new \Exception('Failed to resume processing auth proc chain.'); + } + + /** + * Redirect the user to set up MFA. + * + * @param array $state + */ + public static function redirectToMfaSetup(&$state) + { + $mfaSetupUrl = $state['mfaSetupUrl']; + + // Tell the MFA-setup URL where the user is ultimately trying to go (if known). + $currentDestination = self::getRelayStateUrl($state); + if (! empty($currentDestination)) { + $mfaSetupUrl = HTTP::addURLParameters( + $mfaSetupUrl, + ['returnTo' => $currentDestination] + ); + } + + $logger = LoggerFactory::getAccordingToState($state); + $logger->warning(sprintf( + 'mfa: Sending Employee ID %s to set up MFA at %s', + var_export($state['employeeId'] ?? null, true), + var_export($mfaSetupUrl, true) + )); + + HTTP::redirectTrustedURL($mfaSetupUrl); + } + + /** + * Apply this AuthProc Filter. It will either return (indicating that it + * has completed) or it will redirect the user, in which case it will + * later call `SimpleSAML\Auth\ProcessingChain::resumeProcessing($state)`. + * + * @param array &$state The current state. + */ + public function process(&$state) + { + // Get the necessary info from the state data. + $employeeId = $this->getAttribute($this->employeeIdAttr, $state); + $mfa = $this->getAttributeAllValues('mfa', $state); + $isHeadedToMfaSetupUrl = self::isHeadedToMfaSetupUrl( + $state, + $this->mfaSetupUrl + ); + + $this->logger->debug(json_encode([ + 'module' => 'mfa', + 'event' => 'process', + 'mfa' => $mfa, + 'isHeadedToMfaSetupUrl' => $isHeadedToMfaSetupUrl, + 'employeeId' => $employeeId, + ])); + + + // Record to the state what logger class to use. + $state['loggerClass'] = $this->loggerClass; + + // Add to the state any config data we may need for the low-on/out-of + // backup codes pages. + $state['mfaSetupUrl'] = $this->mfaSetupUrl; + + if (self::shouldPromptForMfa($mfa)) { + if (self::hasMfaOptions($mfa)) { + $this->redirectToMfaPrompt($state, $employeeId, $mfa['options']); + return; + } + + if (! $isHeadedToMfaSetupUrl) { + $this->redirectToMfaNeededMessage($state, $employeeId, $this->mfaSetupUrl); + return; + } + } + + unset($state['Attributes']['manager_email']); + } + + /** + * Redirect the user to a page telling them they must set up MFA. + * + * @param array $state The state data. + * @param string $employeeId The Employee ID of the user account. + * @param string $mfaSetupUrl URL to MFA setup process + */ + protected function redirectToMfaNeededMessage(&$state, $employeeId, $mfaSetupUrl) + { + assert('is_array($state)'); + + $this->logger->info(sprintf( + 'mfa: Redirecting Employee ID %s to must-set-up-MFA message.', + var_export($employeeId, true) + )); + + /* Save state and redirect. */ + $state['employeeId'] = $employeeId; + $state['mfaSetupUrl'] = $mfaSetupUrl; + + $stateId = State::saveState($state, self::STAGE_SENT_TO_MFA_NEEDED_MESSAGE); + $url = Module::getModuleURL('mfa/must-set-up-mfa.php'); + + HTTP::redirectTrustedURL($url, ['StateId' => $stateId]); + } + + /** + * Redirect the user to the appropriate MFA-prompt page. + * + * @param array $state The state data. + * @param string $employeeId The Employee ID of the user account. + * @param array $mfaOptions Array of MFA options + * @throws \Exception + */ + protected function redirectToMfaPrompt(&$state, $employeeId, $mfaOptions) + { + assert('is_array($state)'); + + /** @todo Check for valid remember-me cookies here rather doing a redirect first. */ + + $state['mfaOptions'] = $mfaOptions; + $state['managerEmail'] = self::getManagerEmail($state); + $state['idBrokerConfig'] = [ + 'accessToken' => $this->idBrokerAccessToken, + 'assertValidIp' => $this->idBrokerAssertValidIp, + 'baseUri' => $this->idBrokerBaseUri, + 'clientClass' => $this->idBrokerClientClass, + 'trustedIpRanges' => $this->idBrokerTrustedIpRanges, + ]; + + $this->logger->info(sprintf( + 'mfa: Redirecting Employee ID %s to MFA prompt.', + var_export($employeeId, true) + )); + + /* Save state and redirect. */ + $state['employeeId'] = $employeeId; + $state['rpOrigin'] = 'https://' . $this->idpDomainName; + + $id = State::saveState($state, self::STAGE_SENT_TO_MFA_PROMPT); + $url = Module::getModuleURL('mfa/prompt-for-mfa.php'); + $userAgent = LoginBrowser::getUserAgent(); + $webauthnSupport = LoginBrowser::supportsWebAuthn($userAgent); + + $this->logger->debug(json_encode([ + 'event' => 'check browser', + 'user_agent' => $userAgent, + 'webauthn_support' => $webauthnSupport, + ])); + + $mfaOption = self::getMfaOptionToUse($mfaOptions, $userAgent); + + HTTP::redirectTrustedURL($url, [ + 'mfaId' => $mfaOption['id'], + 'StateId' => $id, + ]); + } + + /** + * Validate that remember me cookie values are legit and valid + * @param string $cookieHash + * @param string $expireDate + * @param $mfaOptions + * @param $state + * @return bool + * @throws \Sil\PhpEnv\EnvVarNotFoundException + */ + public static function isRememberMeCookieValid( + string $cookieHash, + string $expireDate, + $mfaOptions, + $state + ): bool { + $rememberSecret = Env::requireEnv('REMEMBER_ME_SECRET'); + if (! empty($cookieHash) && ! empty($expireDate) && is_numeric($expireDate)) { + // Check if value of expireDate is in future + if ((int)$expireDate > time()) { + $expectedString = self::generateRememberMeCookieString($rememberSecret, $state['employeeId'], $expireDate, $mfaOptions); + return password_verify($expectedString, $cookieHash); + } + } + + return false; + } + + /** + * Generate and return a string to be hashed for remember me cookie + * @param string $rememberSecret + * @param string $employeeId + * @param int $expireDate + * @param array $mfaOptions + * @return string + */ + public static function generateRememberMeCookieString( + string $rememberSecret, + string $employeeId, + int $expireDate, + array $mfaOptions + ): string { + $allMfaIds = ''; + foreach ($mfaOptions as $opt) { + if ($opt['type'] !== 'manager') { + $allMfaIds .= $opt['id']; + } + } + + $string = $rememberSecret . $employeeId . $expireDate . $allMfaIds; + return $string; + } + + /** + * Redirect the user to a page telling them they are running low on backup + * codes and encouraging them to create more now. + * + * NOTE: This function never returns. + * + * @param array $state The state data. + * @param string $employeeId The Employee ID of the user account. + * @param int $numBackupCodesRemaining The number of backup codes that the + * user has left (now that they have used up one for this login). + */ + protected static function redirectToLowOnBackupCodesNag( + array &$state, + $employeeId, + $numBackupCodesRemaining + ) { + $state['employeeId'] = $employeeId; + $state['numBackupCodesRemaining'] = $numBackupCodesRemaining; + + $stateId = State::saveState($state, self::STAGE_SENT_TO_LOW_ON_BACKUP_CODES_NAG); + $url = Module::getModuleURL('mfa/low-on-backup-codes.php'); + + HTTP::redirectTrustedURL($url, ['StateId' => $stateId]); + } + + /** + * Redirect the user to a page telling them they just used up their last + * backup code. + * + * NOTE: This function never returns. + * + * @param array $state The state data. + * @param string $employeeId The Employee ID of the user account. + */ + protected static function redirectToOutOfBackupCodesMessage(array &$state, $employeeId) + { + $state['employeeId'] = $employeeId; + + $stateId = State::saveState($state, self::STAGE_SENT_TO_OUT_OF_BACKUP_CODES_MESSAGE); + $url = Module::getModuleURL('mfa/out-of-backup-codes.php'); + + HTTP::redirectTrustedURL($url, ['StateId' => $stateId]); + } + + /** + * Set cookies c1 and c2 + * @param string $employeeId + * @param array $mfaOptions + * @param string $rememberDuration + * @throws \Sil\PhpEnv\EnvVarNotFoundException + */ + public static function setRememberMeCookies( + string $employeeId, + array $mfaOptions, + string $rememberDuration = '+30 days' + ) { + $rememberSecret = Env::requireEnv('REMEMBER_ME_SECRET'); + $secureCookie = Env::get('SECURE_COOKIE', true); + $expireDate = strtotime($rememberDuration); + $cookieString = self::generateRememberMeCookieString($rememberSecret, $employeeId, $expireDate, $mfaOptions); + $cookieHash = password_hash($cookieString, PASSWORD_DEFAULT); + setcookie('c1', base64_encode($cookieHash), $expireDate, '/', null, $secureCookie, true); + setcookie('c2', $expireDate, $expireDate, '/', null, $secureCookie, true); + } + + protected static function shouldPromptForMfa($mfa) + { + return (strtolower($mfa['prompt']) !== 'no'); + } + + /** + * Send a rescue code to the manager, then redirect the user to a page where they + * can enter the code. + * + * NOTE: This function never returns. + * + * @param array $state The state data. + * @param LoggerInterface $logger A PSR-3 compatible logger. + */ + public static function sendManagerCode(array &$state, $logger) + { + try { + $idBrokerClient = self::getIdBrokerClient($state['idBrokerConfig']); + $mfaOption = $idBrokerClient->mfaCreate($state['employeeId'], 'manager'); + $mfaOption['type'] = 'manager'; + + $logger->warning(json_encode([ + 'event' => 'Manager rescue code sent', + 'employeeId' => $state['employeeId'], + ])); + } catch (\Throwable $t) { + $logger->error(json_encode([ + 'event' => 'Manager rescue code: failed', + 'employeeId' => $state['employeeId'], + 'error' => $t->getCode() . ': ' . $t->getMessage(), + ])); + } + + $mfaOptions = $state['mfaOptions']; + + /* + * Add this option into the list, giving it a key so `mfaOptions` doesn't get multiple entries + * if the user tries multiple times. + */ + $mfaOptions['manager'] = $mfaOption; + $state['mfaOptions'] = $mfaOptions; + $state['managerEmail'] = self::getManagerEmail($state); + $stateId = State::saveState($state, self::STAGE_SENT_TO_MFA_PROMPT); + + $url = Module::getModuleURL('mfa/prompt-for-mfa.php'); + + HTTP::redirectTrustedURL($url, ['mfaId' => $mfaOption['id'], 'StateId' => $stateId]); + } + + /** + * Get masked copy of manager_email, or null if it isn't available. + * + * @param array $state + * @return string|null + */ + public static function getManagerEmail($state) + { + $managerEmail = $state['Attributes']['manager_email'] ?? ['']; + if (empty($managerEmail[0])) { + return null; + } + return self::maskEmail($managerEmail[0]); + } + + /** + * Get the manager MFA, if it exists. Otherwise, return null. + * + * @param array[] $mfaOptions The available MFA options. + * @return array The manager MFA. + * @throws \InvalidArgumentException + */ + public static function getManagerMfa($mfaOptions) + { + foreach ($mfaOptions as $mfaOption) { + if ($mfaOption['type'] === 'manager') { + return $mfaOption; + } + } + + return null; + } + + /** + * @param string $email an email address + * @return string with most letters changed to asterisks + */ + public static function maskEmail($email) + { + list($part1, $domain) = explode('@', $email); + $newEmail = ''; + $useRealChar = true; + + /* + * Replace all characters with '*', except + * the first one, the last one, underscores and each + * character that follows and underscore. + */ + foreach (str_split($part1) as $nextChar) { + if ($useRealChar) { + $newEmail .= $nextChar; + $useRealChar = false; + } else if ($nextChar === '_') { + $newEmail .= $nextChar; + $useRealChar = true; + } else { + $newEmail .= '*'; + } + } + + // replace the last * with the last real character + $newEmail = substr($newEmail, 0, -1); + $newEmail .= substr($part1, -1); + $newEmail .= '@'; + + /* + * Add an '*' for each of the characters of the domain, except + * for the first character of each part and the . + */ + list($domainA, $domainB) = explode('.', $domain); + + $newEmail .= substr($domainA, 0, 1); + $newEmail .= str_repeat('*', strlen($domainA) - 1); + $newEmail .= '.'; + + $newEmail .= substr($domainB, 0, 1); + $newEmail .= str_repeat('*', strlen($domainB) - 1); + return $newEmail; + } + + /** + * @param array $state + * @param LoggerInterface $logger + */ + protected static function updateStateWithNewMfaData(&$state, $logger) + { + $idBrokerClient = self::getIdBrokerClient($state['idBrokerConfig']); + + $log = [ + 'event' => 'Update state with new mfa data', + ]; + + try { + $newMfaOptions = $idBrokerClient->mfaList($state['employeeId']); + } catch (\Exception $e) { + $log['status'] = 'failed: id-broker exception'; + $logger->error(json_encode($log)); + return; + } + + if (empty($newMfaOptions)) { + $log['status'] = 'failed: no data provided'; + $logger->warning(json_encode($log)); + return; + } + + $state['Attributes']['mfa']['options'] = $newMfaOptions; + + $log['data'] = $newMfaOptions; + $log['status'] = 'updated'; + $logger->warning(json_encode($log)); + } +} diff --git a/modules/mfa/lib/LoggerFactory.php b/modules/mfa/lib/LoggerFactory.php new file mode 100644 index 00000000..ffdadd6b --- /dev/null +++ b/modules/mfa/lib/LoggerFactory.php @@ -0,0 +1,41 @@ +getName(); + + // For now, simply set these to approximate the results shown on caniuse: + // https://caniuse.com/?search=webauthn + return in_array( + $browserName, + [ + Browser::CHROME, + Browser::SAFARI, + Browser::EDGE, + Browser::FIREFOX, + Browser::OPERA, + ], + true + ); + } +} diff --git a/modules/mfa/templates/low-on-backup-codes.php b/modules/mfa/templates/low-on-backup-codes.php new file mode 100644 index 00000000..bf1f7a37 --- /dev/null +++ b/modules/mfa/templates/low-on-backup-codes.php @@ -0,0 +1,21 @@ +data['header'] = 'Almost out of Printable Backup Codes'; +$this->includeAtTemplateBase('includes/header.php'); + +$numBackupCodesRemaining = $this->data['numBackupCodesRemaining']; +?> +

+ You are almost out of Printable Backup Codes. + You only have remaining. +

+
+ + + +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/must-set-up-mfa.php b/modules/mfa/templates/must-set-up-mfa.php new file mode 100644 index 00000000..dbb5c4bd --- /dev/null +++ b/modules/mfa/templates/must-set-up-mfa.php @@ -0,0 +1,16 @@ +data['header'] = 'Set up 2-Step Verification'; +$this->includeAtTemplateBase('includes/header.php'); + +?> +

+ Your account requires additional security. + You must set up 2-step verification at this time. +

+
+ +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/new-backup-codes.php b/modules/mfa/templates/new-backup-codes.php new file mode 100644 index 00000000..5624a681 --- /dev/null +++ b/modules/mfa/templates/new-backup-codes.php @@ -0,0 +1,40 @@ +data['header'] = 'New Printable Backup Codes'; +$this->includeAtTemplateBase('includes/header.php'); + +$newBackupCodes = $this->data['newBackupCodes']; +$mfaSetupUrl = $this->data['mfaSetupUrl']; +?> + + +

+ Something went wrong while we were trying to get more Printable Backup Codes for you. +

+

+ We are sorry for the inconvenience. After you finish logging in, please + check your 2-Step Verification methods here:
+ +

+ +

+ Here are your new Printable Backup Codes. Remember to keep them + secret (like a password) and store them somewhere safe. +

+

+

+ Once you have stored them somewhere safe, you are welcome to click the + button below to continue to where you were going. +

+ + +
+ +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/out-of-backup-codes.php b/modules/mfa/templates/out-of-backup-codes.php new file mode 100644 index 00000000..27005578 --- /dev/null +++ b/modules/mfa/templates/out-of-backup-codes.php @@ -0,0 +1,37 @@ +data['header'] = 'Last Printable Backup Code'; +$this->includeAtTemplateBase('includes/header.php'); + +$hasOtherMfaOptions = $this->data['hasOtherMfaOptions']; +?> +

+ You just used your last Printable Backup Code. +

+ + +

+ We recommend you get more now so that you will have some next time we ask + you for one. Otherwise, you will need to use a different option (such as a + Security Key or Smartphone App) the next time we ask you for 2-Step Verification. +

+ +

+ Since you do not have any other 2-Step Verification options set up yet, + you need to get more Printable Backup Codes now so that you will have some + next time we ask you for one. +

+ + +
+ + + + + +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/prompt-for-mfa-backupcode.php b/modules/mfa/templates/prompt-for-mfa-backupcode.php new file mode 100644 index 00000000..3fb79ef5 --- /dev/null +++ b/modules/mfa/templates/prompt-for-mfa-backupcode.php @@ -0,0 +1,57 @@ +data['header'] = '2-Step Verification'; +$this->includeAtTemplateBase('includes/header.php'); + +if (! empty($this->data['errorMessage'])) { + ?> +

+ Oops!
+ data['errorMessage']); ?> +

+ +
+

Printable Backup Code

+

+ Each code can only be used once, so the code you enter this time will be + used up and will not be available again. +

+

+ Enter code: +
+ + +
+ +

+ data['mfaOptions']) > 1): ?> +

+ Don't have your printable backup codes handy? You may also use: +

+
    + data['mfaOptions'] as $mfaOpt) { + if ($mfaOpt['type'] != 'backupcode') { + ?> +
  • + +
+ + data['managerEmail'])): ?> +

+ Can't use any of your 2-Step Verification options? + + Click here for assistance. +

+ +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/prompt-for-mfa-manager.php b/modules/mfa/templates/prompt-for-mfa-manager.php new file mode 100644 index 00000000..643f9631 --- /dev/null +++ b/modules/mfa/templates/prompt-for-mfa-manager.php @@ -0,0 +1,49 @@ +data['header'] = '2-Step Verification'; +$this->includeAtTemplateBase('includes/header.php'); + +if (! empty($this->data['errorMessage'])) { + ?> +

+ Oops!
+ data['errorMessage']); ?> +

+ +
+

Manager Rescue Code

+

+ When you receive your code from your manager, enter it here. +

+

+ Enter code: +
+ + +
+ +

+ data['mfaOptions']) > 1): ?> +

+ You may also use: +

+
    + data['mfaOptions'] as $mfaOpt) { + if ($mfaOpt['type'] != 'manager') { + ?> +
  • + +
+ +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/prompt-for-mfa-totp.php b/modules/mfa/templates/prompt-for-mfa-totp.php new file mode 100644 index 00000000..3603c8e9 --- /dev/null +++ b/modules/mfa/templates/prompt-for-mfa-totp.php @@ -0,0 +1,53 @@ +data['header'] = '2-Step Verification'; +$this->includeAtTemplateBase('includes/header.php'); + +if (! empty($this->data['errorMessage'])) { + ?> +

+ Oops!
+ data['errorMessage']); ?> +

+ +
+

Smartphone App

+

+ Enter 6-digit code: +
+ + +
+ +

+ data['mfaOptions']) > 1): ?> +

+ Don't have your smartphone app handy? You may also use: +

+
    + data['mfaOptions'] as $mfaOpt) { + if ($mfaOpt['type'] != 'totp') { + ?> +
  • + +
+ + data['managerEmail'])): ?> +

+ Can't use any of your 2-Step Verification options? + + Click here for assistance. +

+ +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/prompt-for-mfa-webauthn.php b/modules/mfa/templates/prompt-for-mfa-webauthn.php new file mode 100644 index 00000000..12478937 --- /dev/null +++ b/modules/mfa/templates/prompt-for-mfa-webauthn.php @@ -0,0 +1,79 @@ +data['header'] = '2-Step Verification'; +$this->includeAtTemplateBase('includes/header.php'); +?> +data['supportsWebAuthn']): ?> + + + + +
+

USB Security Key

+ data['supportsWebAuthn']): ?> +

Please insert your security key and press its button.

+

+ + +
+ + + +

+ +

+ USB Security Keys are not supported in your current browser. + Please consider a more secure browser like + Google Chrome. +

+ + + data['mfaOptions']) > 1): ?> +

+ Don't have your security key handy? You may also use: +

+
    + data['mfaOptions'] as $mfaOpt) { + if ($mfaOpt['type'] != 'webauthn') { + ?> +
  • + +
+ + data['managerEmail'])): ?> +

+ Can't use any of your 2-Step Verification options? + + Click here for assistance. +

+ +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/templates/send-manager-mfa.php b/modules/mfa/templates/send-manager-mfa.php new file mode 100644 index 00000000..4a274ac6 --- /dev/null +++ b/modules/mfa/templates/send-manager-mfa.php @@ -0,0 +1,20 @@ +data['header'] = 'Send manager backup code'; +$this->includeAtTemplateBase('includes/header.php'); + +?> +

+ You can send a backup code to your manager to serve as an + additional 2-Step Verification option. + The email address on file (masked for privacy) is data['managerEmail'] ?> +

+
+ + +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/mfa/www/low-on-backup-codes.php b/modules/mfa/www/low-on-backup-codes.php new file mode 100644 index 00000000..8adb6d8c --- /dev/null +++ b/modules/mfa/www/low-on-backup-codes.php @@ -0,0 +1,40 @@ +data['numBackupCodesRemaining'] = $state['numBackupCodesRemaining']; +$t->show(); + +$logger->info(sprintf( + 'mfa: Told Employee ID %s they are low on backup codes.', + $state['employeeId'] +)); diff --git a/modules/mfa/www/must-set-up-mfa.php b/modules/mfa/www/must-set-up-mfa.php new file mode 100644 index 00000000..dbe44428 --- /dev/null +++ b/modules/mfa/www/must-set-up-mfa.php @@ -0,0 +1,32 @@ +show(); + +$logger->info(sprintf( + 'mfa: Told Employee ID %s they they must set up MFA.', + $state['employeeId'] +)); diff --git a/modules/mfa/www/new-backup-codes.php b/modules/mfa/www/new-backup-codes.php new file mode 100644 index 00000000..29573a2a --- /dev/null +++ b/modules/mfa/www/new-backup-codes.php @@ -0,0 +1,39 @@ +data['mfaSetupUrl'] = $state['mfaSetupUrl']; +$t->data['newBackupCodes'] = $state['newBackupCodes'] ?? []; +$t->show(); diff --git a/modules/mfa/www/out-of-backup-codes.php b/modules/mfa/www/out-of-backup-codes.php new file mode 100644 index 00000000..acb9ee65 --- /dev/null +++ b/modules/mfa/www/out-of-backup-codes.php @@ -0,0 +1,42 @@ +data['hasOtherMfaOptions'] = $hasOtherMfaOptions; +$t->show(); + +$logger->info(sprintf( + 'mfa: Told Employee ID %s they are out of backup codes%s.', + $state['employeeId'], + $hasOtherMfaOptions ? '' : ' and must set up more' +)); diff --git a/modules/mfa/www/prompt-for-mfa.php b/modules/mfa/www/prompt-for-mfa.php new file mode 100644 index 00000000..3e63050a --- /dev/null +++ b/modules/mfa/www/prompt-for-mfa.php @@ -0,0 +1,114 @@ +warning(json_encode([ + 'event' => 'MFA skipped due to valid remember-me cookie', + 'employeeId' => $state['employeeId'], + ])); + + unset($state['Attributes']['manager_email']); + + // This condition should never return + ProcessingChain::resumeProcessing($state); + throw new \Exception('Failed to resume processing auth proc chain.'); +} + +$mfaId = filter_input(INPUT_GET, 'mfaId'); +$userAgent = LoginBrowser::getUserAgent(); + +if (empty($mfaId)) { + $logger->critical(json_encode([ + 'event' => 'MFA ID missing in URL. Choosing one and doing a redirect.', + 'employeeId' => $state['employeeId'], + ])); + + // Pick an MFA ID and do a redirect to put that into the URL. + $mfaOption = Mfa::getMfaOptionToUse($mfaOptions, $userAgent); + $moduleUrl = SimpleSAML\Module::getModuleURL('mfa/prompt-for-mfa.php', [ + 'mfaId' => $mfaOption['id'], + 'StateId' => $stateId, + ]); + HTTP::redirectTrustedURL($moduleUrl); + return; +} +$mfaOption = Mfa::getMfaOptionById($mfaOptions, $mfaId); + +// If the user has submitted their MFA value... +if (filter_has_var(INPUT_POST, 'submitMfa')) { + $mfaSubmission = filter_input(INPUT_POST, 'mfaSubmission'); + if (substr($mfaSubmission, 0, 1) == '{') { + $mfaSubmission = json_decode($mfaSubmission, true); + } + + $rememberMe = filter_input(INPUT_POST, 'rememberMe') ?? false; + + // NOTE: This will only return if validation fails. + $errorMessage = Mfa::validateMfaSubmission( + $mfaId, + $state['employeeId'], + $mfaSubmission, + $state, + $rememberMe, + $logger, + $mfaOption['type'], + $state['rpOrigin'] + ); + + $logger->warning(json_encode([ + 'event' => 'MFA validation result: failed', + 'employeeId' => $state['employeeId'], + 'mfaType' => $mfaOption['type'], + 'error' => $errorMessage, + ])); +} + +$globalConfig = Configuration::getInstance(); + +$mfaTemplateToUse = Mfa::getTemplateFor($mfaOption['type']); + +$t = new Template($globalConfig, $mfaTemplateToUse); +$t->data['errorMessage'] = $errorMessage ?? null; +$t->data['mfaOption'] = $mfaOption; +$t->data['mfaOptions'] = $mfaOptions; +$t->data['stateId'] = $stateId; +$t->data['supportsWebAuthn'] = LoginBrowser::supportsWebAuthn($userAgent); +$t->data['managerEmail'] = $state['managerEmail']; +$t->show(); + +$logger->info(json_encode([ + 'event' => 'Prompted user for MFA', + 'employeeId' => $state['employeeId'], + 'mfaType' => $mfaOption['type'], +])); diff --git a/modules/mfa/www/send-manager-mfa.php b/modules/mfa/www/send-manager-mfa.php new file mode 100644 index 00000000..97ae0257 --- /dev/null +++ b/modules/mfa/www/send-manager-mfa.php @@ -0,0 +1,45 @@ + $stateId, + ]); + HTTP::redirectTrustedURL($moduleUrl); +} + +$globalConfig = Configuration::getInstance(); + +$t = new Template($globalConfig, 'mfa:send-manager-mfa.php'); +$t->data['stateId'] = $stateId; +$t->data['managerEmail'] = $state['managerEmail']; +$t->show(); + +$logger->info(json_encode([ + 'event' => 'offer to send manager code', + 'employeeId' => $state['employeeId'], +])); diff --git a/modules/mfa/www/simplewebauthn/LICENSE.md b/modules/mfa/www/simplewebauthn/LICENSE.md new file mode 100644 index 00000000..70730ac2 --- /dev/null +++ b/modules/mfa/www/simplewebauthn/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Matthew Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/mfa/www/simplewebauthn/browser.js b/modules/mfa/www/simplewebauthn/browser.js new file mode 100644 index 00000000..8b1de96e --- /dev/null +++ b/modules/mfa/www/simplewebauthn/browser.js @@ -0,0 +1,2 @@ +/* [@simplewebauthn/browser] Version: 4.1.0 - Wednesday, September 1st, 2021, 9:11:50 AM */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).SimpleWebAuthnBrowser={})}(this,(function(e){"use strict";function t(e){const t=new Uint8Array(e);let n="";for(const e of t)n+=String.fromCharCode(e);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function n(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),n=(4-t.length%4)%4,r=t.padEnd(t.length+n,"="),o=atob(r),i=new ArrayBuffer(o.length),a=new Uint8Array(i);for(let e=0;einitComposerAutoloader(); + assert('is_array($config)'); + + $this->loggerClass = $config['loggerClass'] ?? Psr3SamlLogger::class; + $this->logger = LoggerFactory::get($this->loggerClass); + + $this->loadValuesFromConfig($config, [ + 'profileUrl', + 'employeeIdAttr', + ]); + + $this->mfaLearnMoreUrl = $config['mfaLearnMoreUrl'] ?? null; + $this->profileUrl = $config['profileUrl'] ?? null; + } + + /** + * @param $config + * @param $attributes + * @throws \Exception + */ + protected function loadValuesFromConfig($config, $attributes) + { + foreach ($attributes as $attribute) { + $this->$attribute = $config[$attribute] ?? null; + + self::validateConfigValue( + $attribute, + $this->$attribute, + $this->logger + ); + } + } + + /** + * Validate the given config value + * + * @param string $attribute The name of the attribute. + * @param mixed $value The value to check. + * @param LoggerInterface $logger The logger. + * @throws \Exception + */ + public static function validateConfigValue($attribute, $value, $logger) + { + if (empty($value) || !is_string($value)) { + $exception = new \Exception(sprintf( + 'The value we have for %s (%s) is empty or is not a string', + $attribute, + var_export($value, true) + ), 1507146042); + + $logger->critical($exception->getMessage()); + throw $exception; + } + } + + /** + * Get the specified attribute from the given state data. + * + * NOTE: If the attribute's data is an array, the first value will be + * returned. Otherwise, the attribute's data will simply be returned + * as-is. + * + * @param string $attributeName The name of the attribute. + * @param array $state The state data. + * @return mixed The attribute value, or null if not found. + */ + protected function getAttribute($attributeName, $state) + { + $attributeData = $state['Attributes'][$attributeName] ?? null; + + if (is_array($attributeData)) { + return $attributeData[0] ?? null; + } + + return $attributeData; + } + + /** + * Get all of the values for the specified attribute from the given state + * data. + * + * NOTE: If the attribute's data is an array, it will be returned as-is. + * Otherwise, it will be returned as a single-entry array of the data. + * + * @param string $attributeName The name of the attribute. + * @param array $state The state data. + * @return array|null The attribute's value(s), or null if the attribute was + * not found. + */ + protected function getAttributeAllValues($attributeName, $state) + { + $attributeData = $state['Attributes'][$attributeName] ?? null; + + return is_null($attributeData) ? null : (array)$attributeData; + } + + /** + * Return the saml:RelayState if it begins with "http" or "https". Otherwise + * return an empty string. + * + * @param array $state + * @returns string + * @return mixed|string + */ + protected static function getRelayStateUrl($state) + { + if (array_key_exists('saml:RelayState', $state)) { + $samlRelayState = $state['saml:RelayState']; + + if (strpos($samlRelayState, "http://") === 0) { + return $samlRelayState; + } + + if (strpos($samlRelayState, "https://") === 0) { + return $samlRelayState; + } + } + return ''; + } + + protected function initComposerAutoloader() + { + $path = __DIR__ . '/../../../vendor/autoload.php'; + if (file_exists($path)) { + require_once $path; + } + } + + protected static function isHeadedToProfileUrl($state, $ProfileUrl) + { + if (array_key_exists('saml:RelayState', $state)) { + $currentDestination = self::getRelayStateUrl($state); + if (! empty($currentDestination)) { + return (strpos($currentDestination, $ProfileUrl) === 0); + } + } + return false; + } + + /** + * Redirect the user to set up profile. + * + * @param array $state + */ + public static function redirectToProfile(&$state) + { + $profileUrl = $state['ProfileUrl']; + // Tell the profile-setup URL where the user is ultimately trying to go (if known). + $currentDestination = self::getRelayStateUrl($state); + if (! empty($currentDestination)) { + $profileUrl = HTTP::addURLParameters( + $profileUrl, + ['returnTo' => $currentDestination] + ); + } + + $logger = LoggerFactory::getAccordingToState($state); + $logger->warning(json_encode([ + 'module' => 'profilereview', + 'event' => 'redirect to profile', + 'employeeId' => $state['employeeId'], + 'profileUrl' => $profileUrl, + ])); + + HTTP::redirectTrustedURL($profileUrl); + } + + /** + * Apply this AuthProc Filter. It will either return (indicating that it + * has completed) or it will redirect the user, in which case it will + * later call `SimpleSAML\Auth\ProcessingChain::resumeProcessing($state)`. + * + * @param array &$state The current state. + */ + public function process(&$state) + { + // Get the necessary info from the state data. + $employeeId = $this->getAttribute($this->employeeIdAttr, $state); + $isHeadedToProfileUrl = self::isHeadedToProfileUrl($state, $this->profileUrl); + + $mfa = $this->getAttributeAllValues('mfa', $state); + $method = $this->getAttributeAllValues('method', $state); + $profileReview = $this->getAttribute('profile_review', $state); + + if (! $isHeadedToProfileUrl) { + // Record to the state what logger class to use. + $state['loggerClass'] = $this->loggerClass; + + $state['ProfileUrl'] = $this->profileUrl; + + if (self::needToShow($mfa['add'], self::MFA_ADD_PAGE)) { + $this->redirectToNag($state, $employeeId, self::MFA_ADD_PAGE); + } + + if (self::needToShow($method['add'], self::METHOD_ADD_PAGE)) { + $this->redirectToNag($state, $employeeId, self::METHOD_ADD_PAGE); + } + + if (self::needToShow($profileReview, self::REVIEW_PAGE)) + { + $this->redirectToProfileReview($state, $employeeId, $mfa['options'], $method['options']); + } + } + + $this->logger->warning(json_encode([ + 'module' => 'profilereview', + 'event' => 'no nag/review needed', + 'isHeadedToProfileUrl' => $isHeadedToProfileUrl, + 'profileReview' => $profileReview, + 'mfa.add' => $mfa['add'], + 'method.add' => $method['add'], + 'employeeId' => $employeeId, + ])); + + unset($state['Attributes']['method']); + unset($state['Attributes']['mfa']); + return; + } + + /** + * Redirect user to profile review page unless there is nothing to review + * + * @param array $state The state data. + * @param string $employeeId The Employee ID of the user account. + * @param array $mfaOptions A list of the mfa options. + * @param array $methodOptions A list of the method options. + */ + protected function redirectToProfileReview(&$state, $employeeId, $mfaOptions, $methodOptions) + { + assert('is_array($state)'); + + foreach ($mfaOptions as $key => $mfaOption) { + if ($mfaOption['type'] === 'manager') { + unset ($mfaOptions[$key]); + } + } + + if (count($mfaOptions) == 0 && count($methodOptions) == 0) { + return; + } + + /* Save state and redirect. */ + $state['employeeId'] = $employeeId; + $state['profileUrl'] = $this->profileUrl; + $state['mfaOptions'] = $mfaOptions; + $state['methodOptions'] = $methodOptions; + $state['template'] = 'review.php'; + + $stateId = State::saveState($state, self::STAGE_SENT_TO_NAG); + $url = Module::getModuleURL('profilereview/nag.php'); + + HTTP::redirectTrustedURL($url, array('StateId' => $stateId)); + } + + /** + * @param array $state + * @param string $employeeId + * @param string $template + */ + protected function redirectToNag(&$state, $employeeId, $template) + { + /* Save state and redirect. */ + $state['employeeId'] = $employeeId; + $state['mfaLearnMoreUrl'] = $this->mfaLearnMoreUrl; + $state['profileUrl'] = $this->profileUrl; + $state['template'] = $template; + + $stateId = State::saveState($state, self::STAGE_SENT_TO_NAG); + $url = Module::getModuleURL('profilereview/nag.php'); + + HTTP::redirectTrustedURL($url, array('StateId' => $stateId)); + } + + public static function hasSeenSplashPageRecently(string $page) + { + $session = Session::getSessionFromRequest(); + return (bool)$session->getData( + self::SESSION_TYPE, + $page + ); + } + + public static function skipSplashPagesFor($seconds, string $page) + { + $session = Session::getSessionFromRequest(); + $session->setData( + self::SESSION_TYPE, + $page, + true, + $seconds + ); + $session->save(); + } + + public static function needToShow($flag, $page) + { + $oneDay = 24 * 60 * 60; + if ($flag === 'yes' && ! self::hasSeenSplashPageRecently($page)) { + self::skipSplashPagesFor($oneDay, $page); + return true; + } + return false; + } +} diff --git a/modules/profilereview/lib/LoggerFactory.php b/modules/profilereview/lib/LoggerFactory.php new file mode 100644 index 00000000..83e335d1 --- /dev/null +++ b/modules/profilereview/lib/LoggerFactory.php @@ -0,0 +1,39 @@ +data['header'] = 'Set up Recovery Methods'; +$this->includeAtTemplateBase('includes/header.php'); +?> +

+ Did you know you can provide alternate email addresses for password recovery? +

+

+ We highly encourage you to do this to ensure continuous access and improved security. +

+
+ + + +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/profilereview/templates/nag-for-mfa.php b/modules/profilereview/templates/nag-for-mfa.php new file mode 100644 index 00000000..1e31541e --- /dev/null +++ b/modules/profilereview/templates/nag-for-mfa.php @@ -0,0 +1,28 @@ +data['header'] = 'Set up 2-Step Verification'; +$this->includeAtTemplateBase('includes/header.php'); + +$mfaLearnMoreUrl = $this->data['mfaLearnMoreUrl']; +?> +

+ Did you know you could greatly increase the security of your account by enabling 2-Step Verification? +

+

+ We highly encourage you to do this for your own safety. +

+
+ + + + + +

Learn more

+ +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/profilereview/templates/review.php b/modules/profilereview/templates/review.php new file mode 100644 index 00000000..365d908d --- /dev/null +++ b/modules/profilereview/templates/review.php @@ -0,0 +1,62 @@ +data['header'] = 'Review 2-Step Verification and Password Recovery'; +$this->includeAtTemplateBase('includes/header.php'); + +$profileUrl = $this->data['profileUrl']; + +?> +

+ Please take a moment to review your 2-Step Verification options and + Password Recovery Methods. +

+

+ We highly encourage you to do this for your own safety. +

+

2-Step Verification

+ + + + + + + + data['mfaOptions'] as $option): ?> + + + + + + + +
LabelTypeCreatedLast Used
+

Password Recovery Methods

+ + + + + + + data['methodOptions'] as $option): ?> + + + + + + +
EmailVerifiedCreated
+
+ + + + + +

Go to Profile

+ +
+includeAtTemplateBase('includes/footer.php'); diff --git a/modules/profilereview/www/nag.php b/modules/profilereview/www/nag.php new file mode 100644 index 00000000..7c0e91c0 --- /dev/null +++ b/modules/profilereview/www/nag.php @@ -0,0 +1,45 @@ +data['profileUrl'] = $state['profileUrl']; +$t->data['methodOptions'] = $state['methodOptions']; +$t->data['mfaOptions'] = $state['mfaOptions']; +$t->data['mfaLearnMoreUrl'] = $state['mfaLearnMoreUrl']; +$t->show(); + +$logger->warning(json_encode([ + 'module' => 'profilereview', + 'event' => 'presented nag', + 'template' => $state['template'], + 'employeeId' => $state['employeeId'], +])); diff --git a/modules/profilereview/www/review.php b/modules/profilereview/www/review.php new file mode 100644 index 00000000..aa0cae04 --- /dev/null +++ b/modules/profilereview/www/review.php @@ -0,0 +1,47 @@ +data['profileUrl'] = $state['profileUrl']; +$t->data['methodOptions'] = $state['methodOptions']; +$t->data['mfaOptions'] = $state['mfaOptions']; +$t->show(); + +$logger->warning(json_encode([ + 'module' => 'profilereview', + 'event' => 'presented profile review', + 'employeeId' => $state['employeeId'], +])); diff --git a/modules/silauth/dictionaries/error.definition.json b/modules/silauth/dictionaries/error.definition.json new file mode 100644 index 00000000..52225585 --- /dev/null +++ b/modules/silauth/dictionaries/error.definition.json @@ -0,0 +1,50 @@ +{ + "generic_try_later": { + "en": "Hmm... something went wrong. Please try again later.", + "es": "", + "fr": "", + "ko": "" + }, + "username_required": { + "en": "Please provide a username.", + "es": "", + "fr": "", + "ko": "" + }, + "password_required": { + "en": "Please provide a password.", + "es": "", + "fr": "", + "ko": "" + }, + "invalid_login": { + "en": "There was a problem with that username or password (or that account is disabled). Please try again or contact your organization's help desk.", + "es": "", + "fr": "", + "ko": "" + }, + "need_to_set_acct_password": { + "en": "You need to set your password to finish setting up your account. Please use the forgot password link below.", + "es": "", + "fr": "", + "ko": "" + }, + "rate_limit_seconds": { + "en": "There have been too many failed logins for this account. Please wait about {number} seconds, then try again.", + "es": "", + "fr": "", + "ko": "" + }, + "rate_limit_1_minute": { + "en": "There have been too many failed logins for this account. Please wait a minute, then try again.", + "es": "", + "fr": "", + "ko": "" + }, + "rate_limit_minutes": { + "en": "There have been too many failed logins for this account. Please wait about {number} minutes, then try again.", + "es": "", + "fr": "", + "ko": "" + } +} diff --git a/modules/silauth/lib/Auth/Source/SilAuth.php b/modules/silauth/lib/Auth/Source/SilAuth.php new file mode 100644 index 00000000..6560b4d9 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/SilAuth.php @@ -0,0 +1,160 @@ +authConfig = ConfigManager::getConfigFor('auth', $config); + $this->idBrokerConfig = ConfigManager::getConfigFor('idBroker', $config); + $this->mysqlConfig = ConfigManager::getConfigFor('mysql', $config); + $this->recaptchaConfig = ConfigManager::getConfigFor('recaptcha', $config); + $this->templateData = ConfigManager::getConfigFor('templateData', $config); + + ConfigManager::initializeYii2WebApp(['components' => ['db' => [ + 'dsn' => sprintf( + 'mysql:host=%s;dbname=%s', + $this->mysqlConfig['host'], + $this->mysqlConfig['database'] + ), + 'username' => $this->mysqlConfig['user'], + 'password' => $this->mysqlConfig['password'], + ]]]); + } + + /** + * Initialize login. + * + * This function saves the information about the login, and redirects to a + * login page. + * + * @param array &$state Information about the current authentication. + */ + public function authenticate(&$state) + { + assert('is_array($state)'); + + /* + * Save the identifier of this authentication source, so that we can + * retrieve it later. This allows us to call the login()-function on + * the current object. + */ + $state[self::AUTHID] = $this->authId; + + $state['templateData'] = $this->templateData; + + /* Save the $state-array, so that we can restore it after a redirect. */ + $id = State::saveState($state, self::STAGEID); + + /* + * Redirect to the login form. We include the identifier of the saved + * state array as a parameter to the login form. + */ + $url = Module::getModuleURL('silauth/loginuserpass.php'); + $params = array('AuthState' => $id); + HTTP::redirectTrustedURL($url, $params); + + /* The previous function never returns, so this code is never executed. */ + assert('FALSE'); + } + + protected function getTrustedIpAddresses() + { + $trustedIpAddresses = []; + $ipAddressesString = $this->authConfig['trustedIpAddresses'] ?? ''; + $stringPieces = explode(',', $ipAddressesString); + foreach ($stringPieces as $stringPiece) { + if (! empty($stringPiece)) { + $trustedIpAddresses[] = $stringPiece; + } + } + return $trustedIpAddresses; + } + + protected function login($username, $password) + { + $logger = new Psr3StdOutLogger(); + $captcha = new Captcha($this->recaptchaConfig['secret'] ?? null); + $idBroker = new IdBroker( + $this->idBrokerConfig['baseUri'] ?? null, + $this->idBrokerConfig['accessToken'] ?? null, + $logger, + $this->idBrokerConfig['idpDomainName'], + $this->idBrokerConfig['trustedIpRanges'] ?? [], + $this->idBrokerConfig['assertValidIp'] ?? true + ); + $request = new Request($this->getTrustedIpAddresses()); + $userAgent = Request::getUserAgent() ?: '(unknown)'; + $authenticator = new Authenticator( + $username, + $password, + $request, + $captcha, + $idBroker, + $logger + ); + + if (! $authenticator->isAuthenticated()) { + $authError = $authenticator->getAuthError(); + $logger->warning(json_encode([ + 'event' => 'User/pass authentication result: failure', + 'username' => $username, + 'errorCode' => $authError->getCode(), + 'errorMessageParams' => $authError->getMessageParams(), + 'ipAddresses' => join(',', $request->getIpAddresses()), + 'userAgent' => $userAgent, + ])); + throw new Error([ + 'WRONGUSERPASS', + $authError->getFullSspErrorTag(), + $authError->getMessageParams() + ]); + } + + $logger->warning(json_encode([ + 'event' => 'User/pass authentication result: success', + 'username' => $username, + 'ipAddresses' => join(',', $request->getIpAddresses()), + 'userAgent' => $userAgent, + ])); + + return $authenticator->getUserAttributes(); + } +} diff --git a/modules/silauth/lib/Auth/Source/auth/AuthError.php b/modules/silauth/lib/Auth/Source/auth/AuthError.php new file mode 100644 index 00000000..e46a4abb --- /dev/null +++ b/modules/silauth/lib/Auth/Source/auth/AuthError.php @@ -0,0 +1,72 @@ +code = $code; + $this->messageParams = $messageParams; + } + + public function __toString() + { + return var_export([ + 'code' => $this->code, + 'messageParams' => $this->messageParams, + ], true); + } + + /** + * Get the error code, which will be one of the AuthError::CODE_* constants. + * + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * Get the error string that should be passed to simpleSAMLphp's translate + * function for this AuthError. It will correspond to an entry in the + * appropriate dictionary file provided by this module. + * + * @return string Example: '{silauth:error:generic_try_later}' + */ + public function getFullSspErrorTag() + { + return sprintf( + '{%s:%s}', + 'silauth:error', + $this->getCode() + ); + } + + public function getMessageParams() + { + return $this->messageParams; + } +} diff --git a/modules/silauth/lib/Auth/Source/auth/Authenticator.php b/modules/silauth/lib/Auth/Source/auth/Authenticator.php new file mode 100644 index 00000000..82ca08fa --- /dev/null +++ b/modules/silauth/lib/Auth/Source/auth/Authenticator.php @@ -0,0 +1,363 @@ +logger = $logger; + + /** @todo Check CSRF here, too, if feasible. */ + + if (empty($username)) { + $this->setErrorUsernameRequired(); + return; + } + + if (empty($password)) { + $this->setErrorPasswordRequired(); + return; + } + + $ipAddresses = $request->getUntrustedIpAddresses(); + + if ($this->isBlockedByRateLimit($username, $ipAddresses)) { + $logger->warning(json_encode([ + 'event' => 'Preventing login attempt due to existing rate limit', + 'username' => $username, + 'ipAddresses' => join(',', $ipAddresses), + ])); + $this->setErrorBlockedByRateLimit( + $this->getWaitTimeUntilUnblocked($username, $ipAddresses) + ); + return; + } + + if (self::isCaptchaRequired($username, $ipAddresses)) { + $logger->warning(json_encode([ + 'event' => 'Requiring captcha', + 'username' => $username, + 'ipAddresses' => join(',', $ipAddresses), + ])); + if ( ! $captcha->isValidIn($request)) { + $logger->warning(json_encode([ + 'event' => 'Invalid/missing captcha', + 'username' => $username, + 'ipAddresses' => join(',', $ipAddresses), + ])); + $this->setErrorInvalidLogin(); + return; + } + } + + try { + $authenticatedUser = $idBroker->getAuthenticatedUser( + $username, + $password + ); + } catch (\Exception $e) { + $logger->critical(json_encode([ + 'event' => 'Problem communicating with ID Broker', + 'errorCode' => $e->getCode(), + 'errorMessage' => $e->getMessage(), + 'username' => $username, + 'ipAddresses' => join(',', $ipAddresses), + ])); + $this->setErrorGenericTryLater(); + return; + } + + if ($authenticatedUser === null) { + $this->recordFailedLoginBy($username, $ipAddresses); + + if ($this->isBlockedByRateLimit($username, $ipAddresses)) { + $logger->warning(json_encode([ + 'event' => 'Activating rate-limit block', + 'username' => $username, + 'ipAddresses' => join(',', $ipAddresses), + ])); + $this->setErrorBlockedByRateLimit( + $this->getWaitTimeUntilUnblocked($username, $ipAddresses) + ); + } else { + $this->setErrorInvalidLogin(); + } + return; + } + + // NOTE: If we reach this point, the user successfully authenticated. + + $this->resetFailedLoginsBy($username, $ipAddresses); + + $this->setUserAttributes($authenticatedUser); + } + + /** + * Calculate how many seconds of delay should be required for the given + * number of recent failed login attempts. + * + * @param int $numRecentFailures The number of recent failed login attempts. + * @return int The number of seconds to delay before allowing another such + * login attempt. + */ + public static function calculateSecondsToDelay($numRecentFailures) + { + if ( ! self::isEnoughFailedLoginsToBlock($numRecentFailures)) { + return 0; + } + + $limit = self::BLOCK_AFTER_NTH_FAILED_LOGIN; + $numFailuresPastLimit = $numRecentFailures - $limit; + $numberToUse = max($numFailuresPastLimit, 3); + + return min( + $numberToUse * $numberToUse, + self::MAX_SECONDS_TO_BLOCK + ); + } + + /** + * Get the error information (if any). + * + * @return AuthError|null + */ + public function getAuthError() + { + return $this->authError; + } + + /** + * Get the number of seconds to continue blocking, based on the given number + * of recent failures and the given date/time of the most recent failed + * login attempt. + * + * @param int $numRecentFailures The number of recent failed login attempts. + * @param string|null $mostRecentFailureAt A date/time string for when the + * most recent failed login attempt occurred. If null (meaning there + * have been no recent failures), then zero (0) will be returned. + * @return int The number of seconds + * @throws Exception If an invalid (but non-null) date/time string is given + * for `$mostRecentFailureAt`. + */ + public static function getSecondsUntilUnblocked( + int $numRecentFailures, + $mostRecentFailureAt + ) { + if ($mostRecentFailureAt === null) { + return 0; + } + + $totalSecondsToBlock = self::calculateSecondsToDelay( + $numRecentFailures + ); + + $secondsSinceLastFailure = UtcTime::getSecondsSinceDateTime( + $mostRecentFailureAt + ); + + return UtcTime::getRemainingSeconds( + $totalSecondsToBlock, + $secondsSinceLastFailure + ); + } + + /** + * Get the attributes about the authenticated user. + * + * @return array The user attributes. Example:
+     *     [
+     *         // ...
+     *         'mail' => ['someone@example.com'],
+     *         // ...
+     *     ]
+     *     
+ * @throws \Exception + */ + public function getUserAttributes() + { + if ($this->userAttributes === null) { + throw new \Exception( + "You cannot get the user's attributes until you have authenticated the user.", + 1482270373 + ); + } + + return $this->userAttributes; + } + + /** + * Get a (user friendly) wait time representing how long the user must wait + * until they will no longer be blocked by a rate limit (regardless of + * whether it is their username and/or IP address that is blocked). + * + * NOTE: This will always return a WaitTime, even if the given username and + * IP addresses aren't blocked (in which case the shortest available + * WaitTime will be returned, such as a 5-second wait time). + * + * @param string $username The username in question. + * @param array $ipAddresses The list of relevant IP addresses (related to + * this request). + * @return WaitTime + */ + protected function getWaitTimeUntilUnblocked($username, array $ipAddresses) + { + $durationsInSeconds = [ + FailedLoginUsername::getSecondsUntilUnblocked($username), + ]; + + foreach ($ipAddresses as $ipAddress) { + $durationsInSeconds[] = FailedLoginIpAddress::getSecondsUntilUnblocked($ipAddress); + } + + return WaitTime::getLongestWaitTime($durationsInSeconds); + } + + protected function hasError() + { + return ($this->authError !== null); + } + + /** + * Check whether authentication was successful. If not, call + * getErrorMessage() and/or getErrorCode() to find out why not. + * + * @return bool + */ + public function isAuthenticated() + { + return ( ! $this->hasError()); + } + + protected function isBlockedByRateLimit($username, array $ipAddresses) + { + return FailedLoginUsername::isRateLimitBlocking($username) || + FailedLoginIpAddress::isRateLimitBlockingAnyOfThese($ipAddresses); + } + + public static function isCaptchaRequired($username, array $ipAddresses) + { + return FailedLoginUsername::isCaptchaRequiredFor($username) || + FailedLoginIpAddress::isCaptchaRequiredForAnyOfThese($ipAddresses); + } + + public static function isEnoughFailedLoginsToBlock($numFailedLogins) + { + return ($numFailedLogins >= self::BLOCK_AFTER_NTH_FAILED_LOGIN); + } + + public static function isEnoughFailedLoginsToRequireCaptcha($numFailedLogins) + { + return ($numFailedLogins >= self::REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN); + } + + protected function recordFailedLoginBy($username, array $ipAddresses) + { + FailedLoginUsername::recordFailedLoginBy($username, $this->logger); + FailedLoginIpAddress::recordFailedLoginBy($ipAddresses, $this->logger); + } + + protected function resetFailedLoginsBy($username, array $ipAddresses) + { + FailedLoginUsername::resetFailedLoginsBy($username); + FailedLoginIpAddress::resetFailedLoginsBy($ipAddresses); + } + + protected function setError($code, $messageParams = []) + { + $this->authError = new AuthError($code, $messageParams); + } + + /** + * @param WaitTime $waitTime + */ + protected function setErrorBlockedByRateLimit($waitTime) + { + $unit = $waitTime->getUnit(); + $number = $waitTime->getFriendlyNumber(); + + if ($unit === WaitTime::UNIT_SECOND) { + $errorCode = AuthError::CODE_RATE_LIMIT_SECONDS; + } else { // = minute + if ($number === 1) { + $errorCode = AuthError::CODE_RATE_LIMIT_1_MINUTE; + } else { + $errorCode = AuthError::CODE_RATE_LIMIT_MINUTES; + } + } + + $this->setError($errorCode, ['{number}' => $number]); + } + + protected function setErrorGenericTryLater() + { + $this->setError(AuthError::CODE_GENERIC_TRY_LATER); + } + + protected function setErrorInvalidLogin() + { + $this->setError(AuthError::CODE_INVALID_LOGIN); + } + + protected function setErrorNeedToSetAcctPassword() + { + $this->setError(AuthError::CODE_NEED_TO_SET_ACCT_PASSWORD); + } + + protected function setErrorPasswordRequired() + { + $this->setError(AuthError::CODE_PASSWORD_REQUIRED); + } + + protected function setErrorUsernameRequired() + { + $this->setError(AuthError::CODE_USERNAME_REQUIRED); + } + + protected function setUserAttributes($attributes) + { + $this->userAttributes = $attributes; + } +} diff --git a/modules/silauth/lib/Auth/Source/auth/IdBroker.php b/modules/silauth/lib/Auth/Source/auth/IdBroker.php new file mode 100644 index 00000000..78f1cbd4 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/auth/IdBroker.php @@ -0,0 +1,109 @@ +logger = $logger; + $this->idpDomainName = $idpDomainName; + $this->client = new IdBrokerClient($baseUri, $accessToken, [ + 'http_client_options' => [ + 'timeout' => 10, + ], + IdBrokerClient::TRUSTED_IPS_CONFIG => $trustedIpRanges, + IdBrokerClient::ASSERT_VALID_BROKER_IP_CONFIG => $assertValidIp, + ]); + } + + /** + * Attempt to authenticate with the given username and password, returning + * the attributes for that user if the credentials were acceptable (or null + * if they were not acceptable, since there is no authenticated user in that + * situation). If an unexpected response is received, an exception will be + * thrown. + * + * NOTE: The attributes names used (if any) in the response will be SAML + * field names, not ID Broker field names. + * + * @param string $username The username. + * @param string $password The password. + * @return array|null The user's attributes (if successful), otherwise null. + * @throws \Exception + */ + public function getAuthenticatedUser(string $username, string $password) + { + $rpOrigin = 'https://' . $this->idpDomainName; + $userInfo = $this->client->authenticate($username, $password, $rpOrigin); + + if ($userInfo === null) { + return null; + } + + $pwExpDate = $userInfo['password']['expires_on'] ?? null; + if ($pwExpDate !== null) { + $schacExpiryDate = gmdate('YmdHis\Z', strtotime($pwExpDate)); + } + + return SamlUser::convertToSamlFieldNames( + $userInfo['employee_id'], + $userInfo['first_name'], + $userInfo['last_name'], + $userInfo['username'], + $userInfo['email'], + $userInfo['uuid'], + $this->idpDomainName, + $schacExpiryDate ?? null, + $userInfo['mfa'], + $userInfo['method'], + $userInfo['manager_email'] ?? null, + $userInfo['profile_review'] ?? 'no', + $userInfo['member'] ?? [] + ); + } + + /** + * Ping the /site/status URL. If the ID Broker's status is fine, the + * response string is returned. If not, an exception is thrown. + * + * @return string "OK" + * @throws Exception + */ + public function getSiteStatus() + { + return $this->client->getSiteStatus(); + } +} diff --git a/modules/silauth/lib/Auth/Source/behaviors/CreatedAtUtcBehavior.php b/modules/silauth/lib/Auth/Source/behaviors/CreatedAtUtcBehavior.php new file mode 100644 index 00000000..cc04d8f5 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/behaviors/CreatedAtUtcBehavior.php @@ -0,0 +1,22 @@ +value === null) { + return UtcTime::format(); + } + return parent::getValue($event); + } +} diff --git a/modules/silauth/lib/Auth/Source/captcha/Captcha.php b/modules/silauth/lib/Auth/Source/captcha/Captcha.php new file mode 100644 index 00000000..bffc6d46 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/captcha/Captcha.php @@ -0,0 +1,29 @@ +secret = $secret; + } + + public function isValidIn(Request $request) + { + if (empty($this->secret)) { + throw new \RuntimeException('No captcha secret available.', 1487342411); + } + + $captchaResponse = $request->getCaptchaResponse(); + $ipAddress = $request->getMostLikelyIpAddress(); + + $recaptcha = new \ReCaptcha\ReCaptcha($this->secret); + $rcResponse = $recaptcha->verify($captchaResponse, $ipAddress); + + return $rcResponse->isSuccess(); + } +} diff --git a/modules/silauth/lib/Auth/Source/config/ConfigManager.php b/modules/silauth/lib/Auth/Source/config/ConfigManager.php new file mode 100644 index 00000000..0e95ecb7 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/config/ConfigManager.php @@ -0,0 +1,110 @@ + $value) { + if (Text::startsWith($key, $categoryPrefix)) { + $subKey = self::removeCategory($key); + $categoryConfig[$subKey] = $value; + } + } + return $categoryConfig; + } + + /** + * Get the Yii2 config, merging in the given custom config data. + * + * @param array $customConfig + * @return array + */ + public static function getMergedYii2Config($customConfig) + { + $defaultConfig = require __DIR__ . '/yii2-config.php'; + return array_replace_recursive( + $defaultConfig, + $customConfig + ); + } + + private static function initializeYiiClass() + { + if ( ! class_exists('Yii')) { + require_once __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'; + } + } + + public static function getYii2ConsoleApp($customConfig) + { + self::initializeYiiClass(); + $mergedYii2Config = self::getMergedYii2Config($customConfig); + return new \yii\console\Application($mergedYii2Config); + } + + public static function initializeYii2WebApp($customConfig = []) + { + self::initializeYiiClass(); + + /* Initialize the Yii web application. Note that we do NOT call run() + * here, since we don't want Yii to handle the HTTP request. We just + * want the Yii classes available for use (including database + * models). */ + $app = new \yii\web\Application(self::getMergedYii2Config($customConfig)); + + /* + * Initialize the Yii logger. It doesn't want to initialize itself for some reason. + */ + $app->log->getLogger(); + } + + public static function removeCategory($key) + { + if ($key === null) { + return null; + } + $pieces = explode(self::SEPARATOR, $key, 2); + return end($pieces); + } +} diff --git a/modules/silauth/lib/Auth/Source/config/ssp-config.php b/modules/silauth/lib/Auth/Source/config/ssp-config.php new file mode 100644 index 00000000..63951801 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/config/ssp-config.php @@ -0,0 +1,21 @@ + Env::get('TRUSTED_IP_ADDRESSES'), + 'idBroker.accessToken' => Env::get('ID_BROKER_ACCESS_TOKEN'), + 'idBroker.assertValidIp' => Env::get('ID_BROKER_ASSERT_VALID_IP'), + 'idBroker.baseUri' => Env::get('ID_BROKER_BASE_URI'), + 'idBroker.trustedIpRanges' => Env::getArray('ID_BROKER_TRUSTED_IP_RANGES'), + 'idBroker.idpDomainName' => Env::requireEnv('IDP_DOMAIN_NAME'), + 'mysql.host' => Env::get('MYSQL_HOST'), + 'mysql.database' => Env::get('MYSQL_DATABASE'), + 'mysql.user' => Env::get('MYSQL_USER'), + 'mysql.password' => Env::get('MYSQL_PASSWORD'), + 'recaptcha.siteKey' => Env::get('RECAPTCHA_SITE_KEY'), + 'recaptcha.secret' => Env::get('RECAPTCHA_SECRET'), + 'templateData.profileUrl' => Env::get('PROFILE_URL'), + 'templateData.helpCenterUrl' => Env::get('HELP_CENTER_URL'), +]; diff --git a/modules/silauth/lib/Auth/Source/config/yii2-config.php b/modules/silauth/lib/Auth/Source/config/yii2-config.php new file mode 100644 index 00000000..b3f361c5 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/config/yii2-config.php @@ -0,0 +1,72 @@ + __DIR__ . '/../', + 'id' => 'SilAuth', + 'aliases' => [ + '@SimpleSAML/Module/silauth/Auth/Source' => __DIR__ . '/..', + ], + 'bootstrap' => [ + 'gii', + ], + 'components' => [ + 'db' => [ + 'class' => 'yii\db\Connection', + 'dsn' => null, + 'username' => null, + 'password' => null, + ], + 'log' => [ + 'targets' => [ + [ + 'class' => JsonStreamTarget::class, + 'url' => 'php://stdout', + 'levels' => ['info'], + 'logVars' => [], + 'categories' => ['application'], + 'prefix' => function ($message) { + $prefixData = [ + 'message' => $message, + 'env' => YII_ENV, + ]; + return Json::encode($prefixData); + }, + 'exportInterval' => 1, + ], + [ + 'class' => JsonStreamTarget::class, + 'url' => 'php://stderr', + 'levels' => ['error', 'warning'], + 'logVars' => [], + 'prefix' => function ($message) { + $prefixData = [ + 'message' => $message, + 'env' => YII_ENV, + ]; + return Json::encode($prefixData); + }, + 'exportInterval' => 1, + ], + ], + ], + ], + 'controllerMap' => [ + 'migrate' => [ + 'class' => 'yii\console\controllers\MigrateController', + 'migrationNamespaces' => [ + 'SimpleSAML\\Module\\silauth\\Auth\\Source\\migrations\\', + ], + + // Disable non-namespaced migrations. + 'migrationPath' => null, + ], + ], + 'modules' => [ + 'gii' => [ + 'class' => 'yii\gii\Module', + ], + ], +]; diff --git a/modules/silauth/lib/Auth/Source/csrf/CsrfProtector.php b/modules/silauth/lib/Auth/Source/csrf/CsrfProtector.php new file mode 100644 index 00000000..b913d51f --- /dev/null +++ b/modules/silauth/lib/Auth/Source/csrf/CsrfProtector.php @@ -0,0 +1,81 @@ +session = $session; + } + + public function changeMasterToken() + { + $newMasterToken = $this->generateToken(); + $this->setTokenInSession($newMasterToken); + } + + protected function generateToken() + { + return bin2hex(random_bytes(32)); + } + + /** + * Get the CSRF protection token from the session. If not found, a new one + * will be generated and stored in the session. + * + * @return string The master (aka. authoritative) CSRF token. + */ + public function getMasterToken() + { + $masterToken = $this->getTokenFromSession(); + if (empty($masterToken)) { + $masterToken = $this->generateToken(); + $this->setTokenInSession($masterToken); + } + return $masterToken; + } + + protected function getTokenFromSession() + { + return $this->session->getData( + $this->csrfTokenDataType, + $this->csrfSessionKey + ); + } + + /** + * Check the given CSRF token to see if it was correct. + * + * @param string $submittedToken The CSRF protection token provided by the + * HTTP request. + * @return bool + */ + public function isTokenCorrect($submittedToken) + { + return hash_equals($this->getMasterToken(), $submittedToken); + } + + protected function setTokenInSession($masterToken) + { + $this->session->setData( + $this->csrfTokenDataType, + $this->csrfSessionKey, + $masterToken + ); + } +} diff --git a/modules/silauth/lib/Auth/Source/http/Request.php b/modules/silauth/lib/Auth/Source/http/Request.php new file mode 100644 index 00000000..fe6f242c --- /dev/null +++ b/modules/silauth/lib/Auth/Source/http/Request.php @@ -0,0 +1,212 @@ +isValidIpAddress($ipAddress)) { + $this->trustIpAddress($ipAddress); + } else { + $this->trustIpAddressRange($ipAddress); + } + } + } + + public function getCaptchaResponse() + { + return self::sanitizeInputString(INPUT_POST, 'g-recaptcha-response'); + } + + /** + * Get the list of IP addresses from the current HTTP request. They will be + * in order such that the last IP address in the list belongs to the device + * that most recently handled the request (probably our load balancer). The + * IP address first in the list is both (A) more likely to be the user's + * actual IP address and (B) most likely to be forged. + * + * @return string[] A list of IP addresses. + */ + public function getIpAddresses() + { + $ipAddresses = []; + + // First add the X-Forwarded-For IP addresses. + $xForwardedFor = self::sanitizeInputString( + INPUT_SERVER, + 'HTTP_X_FORWARDED_FOR' + ); + foreach (explode(',', $xForwardedFor) as $xffIpAddress) { + $trimmedIpAddress = trim($xffIpAddress); + if (self::isValidIpAddress($trimmedIpAddress)) { + $ipAddresses[] = $trimmedIpAddress; + } + } + + /* Finally, add the REMOTE_ADDR server value, since it belongs to the + * device that directly passed this request to our application. */ + $remoteAddr = self::sanitizeInputString(INPUT_SERVER, 'REMOTE_ADDR'); + if (self::isValidIpAddress($remoteAddr)) { + $ipAddresses[] = $remoteAddr; + } + + return $ipAddresses; + } + + /** + * Get the IP address that this request most likely originated from. + * + * @return string|null An IP address, or null if none was available. + */ + public function getMostLikelyIpAddress() + { + $untrustedIpAddresses = $this->getUntrustedIpAddresses(); + + /* Given the way X-Forwarded-For (and other?) headers work, the last + * entry in the list was the IP address of the system closest to our + * application. Once we filter out trusted IP addresses (such as that of + * our load balancer, etc.), the last remaining IP address in the list + * is probably the one we care about: + * + * "Since it is easy to forge an X-Forwarded-For field the given + * information should be used with care. The last IP address is always + * the IP address that connects to the last proxy, which means it is + * the most reliable source of information." + * - https://en.wikipedia.org/wiki/X-Forwarded-For + */ + $userIpAddress = end($untrustedIpAddresses); + + /* Make sure we actually ended up with an IP address (not FALSE, which + * `last()` would return if there were no entries). */ + return self::isValidIpAddress($userIpAddress) ? $userIpAddress : null; + } + + /** + * Retrieve input data (see `filter_input(...)` for details) as a string but + * DO NOT sanitize it. If it is a string, it will be returned as is. If it + * is not a string, an empty string will be returned, so that the return + * type will always be a string. + * + * @param int $inputType Example: INPUT_POST + * @param string $variableName Example: 'username' + * @return string + */ + public static function getRawInputString(int $inputType, string $variableName) + { + $input = filter_input($inputType, $variableName); + return is_string($input) ? $input : ''; + } + + public function getUntrustedIpAddresses() + { + $untrustedIpAddresses = []; + foreach ($this->getIpAddresses() as $ipAddress) { + if ( ! $this->isTrustedIpAddress($ipAddress)) { + $untrustedIpAddresses[] = $ipAddress; + } + } + return $untrustedIpAddresses; + } + + /** + * Get the User-Agent string. + * + * @return string The UA string, or an empty string if not found. + */ + public static function getUserAgent() + { + return self::sanitizeInputString(INPUT_SERVER, 'HTTP_USER_AGENT'); + } + + /** + * Determine whether the given IP address is trusted (either specifically or + * because it is in a trusted range). + * + * @param string $ipAddress The IP address in question. + * @return bool + */ + public function isTrustedIpAddress($ipAddress) + { + foreach ($this->trustedIpAddresses as $trustedIp) { + if ($trustedIp->numeric() === IP::create($ipAddress)->numeric()) { + return true; + } + } + + foreach ($this->trustedIpAddressRanges as $trustedIpBlock) { + if ($trustedIpBlock->containsIP($ipAddress)) { + return true; + } + } + + return false; + } + + /** + * Check that a given string is a valid IP address (IPv4 or IPv6). + * + * @param string $ipAddress The IP address in question. + * @return bool + */ + public static function isValidIpAddress($ipAddress) + { + $flags = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6; + return (filter_var($ipAddress, FILTER_VALIDATE_IP, $flags) !== false); + } + + /** + * Retrieve input data (see `filter_input(...)` for details) and sanitize + * it (see Text::sanitizeString). + * + * @param int $inputType Example: INPUT_POST + * @param string $variableName Example: 'username' + * @return string + */ + public static function sanitizeInputString(int $inputType, string $variableName) + { + return Text::sanitizeString(filter_input($inputType, $variableName)); + } + + public function trustIpAddress($ipAddress) + { + if ( ! self::isValidIpAddress($ipAddress)) { + throw new \InvalidArgumentException(sprintf( + '%s is not a valid IP address.', + var_export($ipAddress, true) + )); + } + $this->trustedIpAddresses[] = IP::create($ipAddress); + } + + public function trustIpAddressRange($ipAddressRangeString) + { + $ipBlock = IPBlock::create($ipAddressRangeString); + $this->trustedIpAddressRanges[] = $ipBlock; + } +} diff --git a/modules/silauth/lib/Auth/Source/migrations/M161213135750CreateInitialTables.php b/modules/silauth/lib/Auth/Source/migrations/M161213135750CreateInitialTables.php new file mode 100644 index 00000000..bc0dab3d --- /dev/null +++ b/modules/silauth/lib/Auth/Source/migrations/M161213135750CreateInitialTables.php @@ -0,0 +1,62 @@ +createTable('{{user}}', [ + 'id' => 'pk', + 'uuid' => 'string NOT NULL', + 'employee_id' => 'string NOT NULL', + 'first_name' => 'string NOT NULL', + 'last_name' => 'string NOT NULL', + 'username' => 'string NOT NULL', + 'email' => 'string NOT NULL', + 'password_hash' => 'string NULL', + 'active' => "enum('Yes','No') NOT NULL DEFAULT 'Yes'", + 'locked' => "enum('No','Yes') NOT NULL DEFAULT 'No'", + 'login_attempts' => 'integer NOT NULL DEFAULT 0', + 'block_until' => 'datetime NULL', + 'last_updated' => 'datetime NOT NULL', + ], 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci'); + $this->createIndex('uq_user_uuid', '{{user}}', 'uuid', true); + $this->createIndex('uq_user_employee_id', '{{user}}', 'employee_id', true); + $this->createIndex('uq_user_username', '{{user}}', 'username', true); + $this->createIndex('uq_user_email', '{{user}}', 'email', true); + + $this->createTable('{{previous_password}}', [ + 'id' => 'pk', + 'user_id' => 'integer NOT NULL', + 'password_hash' => 'string NOT NULL', + 'created' => 'datetime NOT NULL', + ], 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci'); + $this->addForeignKey( + 'fk_prev_pw_user_user_id', + '{{previous_password}}', + 'user_id', + '{{user}}', + 'id', + 'CASCADE', + 'CASCADE' + ); + } + + public function safeDown() + { + $this->dropForeignKey( + 'fk_prev_pw_user_user_id', + '{{previous_password}}' + ); + $this->dropTable('{{previous_password}}'); + + $this->dropIndex('uq_user_uuid', '{{user}}'); + $this->dropIndex('uq_user_employee_id', '{{user}}'); + $this->dropIndex('uq_user_username', '{{user}}'); + $this->dropIndex('uq_user_email', '{{user}}'); + $this->dropTable('{{user}}'); + } +} diff --git a/modules/silauth/lib/Auth/Source/migrations/M161213150831SwitchToUtcForDateTimes.php b/modules/silauth/lib/Auth/Source/migrations/M161213150831SwitchToUtcForDateTimes.php new file mode 100644 index 00000000..c9d38f96 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/migrations/M161213150831SwitchToUtcForDateTimes.php @@ -0,0 +1,22 @@ +renameColumn('{{user}}', 'block_until', 'block_until_utc'); + $this->renameColumn('{{user}}', 'last_updated', 'last_updated_utc'); + $this->renameColumn('{{previous_password}}', 'created', 'created_utc'); + } + + public function safeDown() + { + $this->renameColumn('{{previous_password}}', 'created_utc', 'created'); + $this->renameColumn('{{user}}', 'last_updated_utc', 'last_updated'); + $this->renameColumn('{{user}}', 'block_until_utc', 'block_until'); + } +} diff --git a/modules/silauth/lib/Auth/Source/migrations/M170214141109CreateFailedLoginsTable.php b/modules/silauth/lib/Auth/Source/migrations/M170214141109CreateFailedLoginsTable.php new file mode 100644 index 00000000..acba2e8a --- /dev/null +++ b/modules/silauth/lib/Auth/Source/migrations/M170214141109CreateFailedLoginsTable.php @@ -0,0 +1,39 @@ +createTable('{{failed_logins}}', [ + 'id' => 'pk', + 'username' => 'string NOT NULL', + 'ip_address' => 'varchar(45) NOT NULL', + 'occurred_at_utc' => 'datetime NOT NULL', + ], 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci'); + $this->createIndex( + 'idx_failed_logins_username', + '{{failed_logins}}', + 'username', + false + ); + $this->createIndex( + 'idx_failed_logins_ip_address', + '{{failed_logins}}', + 'ip_address', + false + ); + } + + public function safeDown() + { + $this->dropIndex('idx_failed_logins_ip_address', '{{failed_logins}}'); + $this->dropIndex('idx_failed_logins_username', '{{failed_logins}}'); + $this->dropTable('{{failed_logins}}'); + } +} diff --git a/modules/silauth/lib/Auth/Source/migrations/M170214145629RemoveOldTables.php b/modules/silauth/lib/Auth/Source/migrations/M170214145629RemoveOldTables.php new file mode 100644 index 00000000..0b407f9d --- /dev/null +++ b/modules/silauth/lib/Auth/Source/migrations/M170214145629RemoveOldTables.php @@ -0,0 +1,30 @@ +dropForeignKey( + 'fk_prev_pw_user_user_id', + '{{previous_password}}' + ); + $this->dropTable('{{previous_password}}'); + + $this->dropIndex('uq_user_uuid', '{{user}}'); + $this->dropIndex('uq_user_employee_id', '{{user}}'); + $this->dropIndex('uq_user_username', '{{user}}'); + $this->dropIndex('uq_user_email', '{{user}}'); + $this->dropTable('{{user}}'); + } + + public function safeDown() + { + echo "M170214145629RemoveOldTables cannot be reverted.\n"; + + return false; + } +} diff --git a/modules/silauth/lib/Auth/Source/migrations/M170215141724SplitFailedLoginsTable.php b/modules/silauth/lib/Auth/Source/migrations/M170215141724SplitFailedLoginsTable.php new file mode 100644 index 00000000..305e21fe --- /dev/null +++ b/modules/silauth/lib/Auth/Source/migrations/M170215141724SplitFailedLoginsTable.php @@ -0,0 +1,44 @@ +dropIndex('idx_failed_logins_ip_address', '{{failed_logins}}'); + $this->dropIndex('idx_failed_logins_username', '{{failed_logins}}'); + + // Split/update table and add new indexes. + $this->dropColumn('{{failed_logins}}', 'ip_address'); + $this->renameTable('{{failed_logins}}', '{{failed_login_username}}'); + $this->createIndex( + 'idx_failed_login_username_username', + '{{failed_login_username}}', + 'username', + false + ); + /* The max length needed to store an IP address is 45 characters. See + * http://stackoverflow.com/a/1076755/3813891 for details. */ + $this->createTable('{{failed_login_ip_address}}', [ + 'id' => 'pk', + 'ip_address' => 'varchar(45) NOT NULL', + 'occurred_at_utc' => 'datetime NOT NULL', + ], 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci'); + $this->createIndex( + 'idx_failed_login_ip_address_ip_address', + '{{failed_login_ip_address}}', + 'ip_address', + false + ); + } + + public function safeDown() + { + echo "M170215141724SplitFailedLoginsTable cannot be reverted.\n"; + return false; + } +} diff --git a/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddress.php b/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddress.php new file mode 100644 index 00000000..ce192668 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddress.php @@ -0,0 +1,159 @@ + Yii::t('app', 'IP Address'), + 'occurred_at_utc' => Yii::t('app', 'Occurred At (UTC)'), + ]); + } + + public function behaviors() + { + return [ + [ + 'class' => CreatedAtUtcBehavior::className(), + 'attributes' => [ + Model::EVENT_BEFORE_VALIDATE => 'occurred_at_utc', + ], + ], + ]; + } + + public static function countRecentFailedLoginsFor($ipAddress) + { + return self::find()->where([ + 'ip_address' => strtolower($ipAddress), + ])->andWhere([ + '>=', 'occurred_at_utc', UtcTime::format('-60 minutes') + ])->count(); + } + + public static function getFailedLoginsFor($ipAddress) + { + if ( ! Request::isValidIpAddress($ipAddress)) { + throw new \InvalidArgumentException(sprintf( + '%s is not a valid IP address.', + var_export($ipAddress, true) + )); + } + + return self::findAll(['ip_address' => strtolower($ipAddress)]); + } + + /** + * Get the most recent failed-login record for the given IP address, or null + * if none is found. + * + * @param string $ipAddress The IP address. + * @return FailedLoginIpAddress|null + */ + public static function getMostRecentFailedLoginFor($ipAddress) + { + return self::find()->where([ + 'ip_address' => strtolower($ipAddress), + ])->orderBy([ + 'occurred_at_utc' => SORT_DESC, + ])->one(); + } + + /** + * Get the number of seconds remaining until the specified IP address is + * no longer blocked by a rate-limit. Returns zero if it is not currently + * blocked. + * + * @param string $ipAddress The IP address in question + * @return int The number of seconds + */ + public static function getSecondsUntilUnblocked($ipAddress) + { + $failedLogin = self::getMostRecentFailedLoginFor($ipAddress); + + return Authenticator::getSecondsUntilUnblocked( + self::countRecentFailedLoginsFor($ipAddress), + $failedLogin->occurred_at_utc ?? null + ); + } + + public function init() + { + $this->initializeLogger(); + parent::init(); + } + + public static function isCaptchaRequiredFor($ipAddress) + { + return Authenticator::isEnoughFailedLoginsToRequireCaptcha( + self::countRecentFailedLoginsFor($ipAddress) + ); + } + + public static function isCaptchaRequiredForAnyOfThese(array $ipAddresses) + { + foreach ($ipAddresses as $ipAddress) { + if (self::isCaptchaRequiredFor($ipAddress)) { + return true; + } + } + return false; + } + + public static function isRateLimitBlocking($ipAddress) + { + $secondsUntilUnblocked = self::getSecondsUntilUnblocked($ipAddress); + return ($secondsUntilUnblocked > 0); + } + + public static function isRateLimitBlockingAnyOfThese($ipAddresses) + { + foreach ($ipAddresses as $ipAddress) { + if (self::isRateLimitBlocking($ipAddress)) { + return true; + } + } + return false; + } + + public static function recordFailedLoginBy( + array $ipAddresses, + LoggerInterface $logger + ) { + foreach ($ipAddresses as $ipAddress) { + $newRecord = new FailedLoginIpAddress(['ip_address' => strtolower($ipAddress)]); + + if ( ! $newRecord->save()) { + $logger->critical(json_encode([ + 'event' => 'Failed to update login attempts counter in ' + . 'database, so unable to rate limit that IP address.', + 'ipAddress' => $ipAddress, + 'errors' => $newRecord->getErrors(), + ])); + } + } + } + + public static function resetFailedLoginsBy(array $ipAddresses) + { + foreach ($ipAddresses as $ipAddress) { + self::deleteAll(['ip_address' => strtolower($ipAddress)]); + } + } +} diff --git a/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddressBase.php b/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddressBase.php new file mode 100644 index 00000000..0675e2e0 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/models/FailedLoginIpAddressBase.php @@ -0,0 +1,47 @@ + 45], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'ip_address' => Yii::t('app', 'Ip Address'), + 'occurred_at_utc' => Yii::t('app', 'Occurred At Utc'), + ]; + } +} diff --git a/modules/silauth/lib/Auth/Source/models/FailedLoginUsername.php b/modules/silauth/lib/Auth/Source/models/FailedLoginUsername.php new file mode 100644 index 00000000..771132fc --- /dev/null +++ b/modules/silauth/lib/Auth/Source/models/FailedLoginUsername.php @@ -0,0 +1,140 @@ + Yii::t('app', 'Occurred At (UTC)'), + ]); + } + + public function behaviors() + { + return [ + [ + 'class' => CreatedAtUtcBehavior::className(), + 'attributes' => [ + Model::EVENT_BEFORE_VALIDATE => 'occurred_at_utc', + ], + ], + ]; + } + + public static function countRecentFailedLoginsFor($username) + { + return self::find()->where([ + 'username' => strtolower($username), + ])->andWhere([ + '>=', 'occurred_at_utc', UtcTime::format('-60 minutes') + ])->count(); + } + + /** + * Find the records with the given username (if any). + * + * @param string $username The username. + * @return FailedLoginUsername[] An array of any matching records. + */ + public static function getFailedLoginsFor($username) + { + return self::findAll(['username' => strtolower($username)]); + } + + /** + * Get the most recent failed-login record for the given username, or null + * if none is found. + * + * @param string $username The username. + * @return FailedLoginUsername|null + */ + public static function getMostRecentFailedLoginFor($username) + { + return self::find()->where([ + 'username' => strtolower($username), + ])->orderBy([ + 'occurred_at_utc' => SORT_DESC, + ])->one(); + } + + /** + * Get the number of seconds remaining until the specified username is + * no longer blocked by a rate-limit. Returns zero if the user is not + * currently blocked. + * + * @param string $username The username in question + * @return int The number of seconds + */ + public static function getSecondsUntilUnblocked($username) + { + $failedLogin = self::getMostRecentFailedLoginFor($username); + + return Authenticator::getSecondsUntilUnblocked( + self::countRecentFailedLoginsFor($username), + $failedLogin->occurred_at_utc ?? null + ); + } + + public function init() + { + $this->initializeLogger(); + parent::init(); + } + + /** + * Find out whether a rate limit is blocking the specified username. + * + * @param string $username The username + * @return bool + */ + public static function isRateLimitBlocking($username) + { + $secondsUntilUnblocked = self::getSecondsUntilUnblocked($username); + return ($secondsUntilUnblocked > 0); + } + + public static function isCaptchaRequiredFor($username) + { + if (empty($username)) { + return false; + } + return Authenticator::isEnoughFailedLoginsToRequireCaptcha( + self::countRecentFailedLoginsFor($username) + ); + } + + public static function recordFailedLoginBy( + $username, + LoggerInterface $logger + ) { + $newRecord = new FailedLoginUsername(['username' => strtolower($username)]); + if ( ! $newRecord->save()) { + $logger->critical(json_encode([ + 'event' => 'Failed to update login attempts counter in ' + . 'database, so unable to rate limit that username.', + 'username' => $username, + 'errors' => $newRecord->getErrors(), + ])); + } + } + + public static function resetFailedLoginsBy($username) + { + self::deleteAll(['username' => strtolower($username)]); + } +} diff --git a/modules/silauth/lib/Auth/Source/models/FailedLoginUsernameBase.php b/modules/silauth/lib/Auth/Source/models/FailedLoginUsernameBase.php new file mode 100644 index 00000000..a774ed47 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/models/FailedLoginUsernameBase.php @@ -0,0 +1,47 @@ + 255], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'username' => Yii::t('app', 'Username'), + 'occurred_at_utc' => Yii::t('app', 'Occurred At Utc'), + ]; + } +} diff --git a/modules/silauth/lib/Auth/Source/rebuildbasemodels.sh b/modules/silauth/lib/Auth/Source/rebuildbasemodels.sh new file mode 100644 index 00000000..50729c99 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/rebuildbasemodels.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +TABLES=(user previous_password) +SUFFIX="Base" + +declare -A models +models["failed_login_username"]="FailedLoginUsernameBase" +models["failed_login_ip_address"]="FailedLoginIpAddressBase" + +for i in "${!models[@]}"; do + CMD="/data/src/yii gii/model --tableName=$i --modelClass=${models[$i]} --generateRelations=all --enableI18N=1 --overwrite=1 --interactive=0 --ns=SimpleSAML\Module\silauth\Auth\Source\models" + echo $CMD + $CMD +done diff --git a/modules/silauth/lib/Auth/Source/saml/User.php b/modules/silauth/lib/Auth/Source/saml/User.php new file mode 100644 index 00000000..5986f5d1 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/saml/User.php @@ -0,0 +1,61 @@ + [ + $username . '@' . $idpDomainName, + ], + + /** + * Don't use this misspelled version of eduPersonTargetedID. (Accidentally used in the past) + * @deprecated + * + * NOTE: Do NOT include eduPersonTargetedID. If you need it, use the + * core:TargetedID module (at the Hub, if using one) to generate an + * eduPersonTargetedID based on the eduPersonUniqueId attribute (below). + * + */ + 'eduPersonTargetID' => (array)$uuid, // Incorrect, deprecated + + /** + * Use this for a globally unique, non-human friendly, non-reassignable attribute + **/ + 'eduPersonUniqueId' => (array)$eduPersonUniqueId, + + 'sn' => (array)$lastName, + 'givenName' => (array)$firstName, + 'mail' => (array)$email, + 'employeeNumber' => (array)$employeeId, + 'cn' => (array)$username, + 'schacExpiryDate' => (array)$passwordExpirationDate, + 'mfa' => $mfa, + 'method' => $method, + 'uuid' => (array)$uuid, + 'manager_email' => [$managerEmail ?? ''], + 'profile_review' => [$profileReview], + 'member' => $member, + ]; + } +} diff --git a/modules/silauth/lib/Auth/Source/system/System.php b/modules/silauth/lib/Auth/Source/system/System.php new file mode 100644 index 00000000..ea576186 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/system/System.php @@ -0,0 +1,96 @@ +logger = $logger ?? new NullLogger(); + } + + protected function isDatabaseOkay() + { + try { + FailedLoginIpAddress::getMostRecentFailedLoginFor(''); + return true; + } catch (Throwable $t) { + $this->logError($t->getMessage()); + return false; + } + } + + protected function isRequiredConfigPresent() + { + $globalConfig = Configuration::getInstance(); + + /* + * NOTE: We require that SSP's baseurlpath configuration is set (and + * matches the corresponding RegExp) in order to prevent a + * security hole in \SimpleSAML\Utils\HTTP::getBaseURL() where the + * HTTP_HOST value (provided by the user's request) is used to + * build a trusted URL (see SimpleSaml\Module::authenticate()). + */ + $baseURL = $globalConfig->getString('baseurlpath', ''); + $avoidsSecurityHole = (preg_match('#^https?://.*/$#D', $baseURL) === 1); + return $avoidsSecurityHole; + } + + /** + * Check the status of the system, and throw an exception (that is safe to + * show to the public) if any serious error conditions are found. Log any + * problems, even if recoverable. + * + * @throws \Exception + */ + public function reportStatus() + { + if ( ! $this->isRequiredConfigPresent()) { + $this->reportError('Config problem', 1485984755); + } + + if ( ! $this->isDatabaseOkay()) { + $this->reportError('Database problem', 1485284407); + } + + echo 'OK'; + } + + /** + * Add an entry to our log about this error. + * + * @param string $message The error message. + */ + protected function logError($message) + { + $this->logger->error($message); + } + + /** + * Log this error and throw an exception (with an error message) for the + * calling code to handle. + * + * @param string $message The error message. + * @param int $code An error code. + * @throws \Exception + */ + protected function reportError($message, $code) + { + $this->logError($message); + throw new \Exception($message, $code); + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/bootstrap.php b/modules/silauth/lib/Auth/Source/tests/bootstrap.php new file mode 100644 index 00000000..63c9a040 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/bootstrap.php @@ -0,0 +1,14 @@ + ['db' => [ + 'dsn' => sprintf( + 'mysql:host=%s;dbname=%s', + Env::get('MYSQL_HOST'), + Env::get('MYSQL_DATABASE') + ), + 'username' => Env::get('MYSQL_USER'), + 'password' => Env::get('MYSQL_PASSWORD'), +]]]); diff --git a/modules/silauth/lib/Auth/Source/tests/fakes/FakeFailedIdBroker.php b/modules/silauth/lib/Auth/Source/tests/fakes/FakeFailedIdBroker.php new file mode 100644 index 00000000..a2a04fc7 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/fakes/FakeFailedIdBroker.php @@ -0,0 +1,21 @@ +logger->info('FAKE FAILURE: rejecting {username} and {password}.', [ + 'username' => var_export($username, true), + 'password' => var_export($password, true), + ]); + return parent::getAuthenticatedUser($username, $password); + } + + protected function getDesiredResponse() + { + return new Response(400); + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/fakes/FakeIdBroker.php b/modules/silauth/lib/Auth/Source/tests/fakes/FakeIdBroker.php new file mode 100644 index 00000000..f4a0bea4 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/fakes/FakeIdBroker.php @@ -0,0 +1,47 @@ +client = new IdBrokerClient($baseUri, $accessToken, [ + 'http_client_options' => [ + 'handler' => HandlerStack::create(new MockHandler( + + /* Set up several, since this may be called multiple times + * during a test: */ + array_fill( + 0, + Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN * 2, + $this->getDesiredResponse() + ) + )), + ], + IdBrokerClient::ASSERT_VALID_BROKER_IP_CONFIG => false, + ]); + } + + abstract protected function getDesiredResponse(); +} diff --git a/modules/silauth/lib/Auth/Source/tests/fakes/FakeInvalidIdBroker.php b/modules/silauth/lib/Auth/Source/tests/fakes/FakeInvalidIdBroker.php new file mode 100644 index 00000000..8e54e8f7 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/fakes/FakeInvalidIdBroker.php @@ -0,0 +1,18 @@ +logger->info('FAKE ERROR: invalid/unexpected response.'); + return parent::getAuthenticatedUser($username, $password); + } + + protected function getDesiredResponse() + { + return new Response(404); + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/fakes/FakeSuccessfulIdBroker.php b/modules/silauth/lib/Auth/Source/tests/fakes/FakeSuccessfulIdBroker.php new file mode 100644 index 00000000..21d36b25 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/fakes/FakeSuccessfulIdBroker.php @@ -0,0 +1,41 @@ +logger->info('FAKE SUCCESS: accepting {username} and {password}.', [ + 'username' => var_export($username, true), + 'password' => var_export($password, true), + ]); + return parent::getAuthenticatedUser($username, $password); + } + + protected function getDesiredResponse() + { + return new Response(200, [], json_encode([ + 'uuid' => '11111111-aaaa-1111-aaaa-111111111111', + 'employee_id' => '123', + 'first_name' => 'John', + 'last_name' => 'Smith', + 'display_name' => 'John Smith', + 'username' => 'john_smith', + 'email' => 'john_smith@example.com', + 'locked' => 'no', + 'mfa' => [ + 'prompt' => 'no', + 'add' => 'no', + 'review' => 'no', + 'options' => [], + ], + 'method' => [ + 'add' => 'no', + 'review' => 'no', + 'options' => [], + ], + ])); + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/phpunit.xml b/modules/silauth/lib/Auth/Source/tests/phpunit.xml new file mode 100644 index 00000000..052c4a07 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/phpunit.xml @@ -0,0 +1,4 @@ + + diff --git a/modules/silauth/lib/Auth/Source/tests/unit/auth/AuthenticatorTest.php b/modules/silauth/lib/Auth/Source/tests/unit/auth/AuthenticatorTest.php new file mode 100644 index 00000000..7bc84461 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/auth/AuthenticatorTest.php @@ -0,0 +1,97 @@ + 0, 'expected' => 0], + ['failedLoginAttempts' => $blockAfterNth - 1, 'expected' => 0], + ['failedLoginAttempts' => $blockAfterNth, 'expected' => 9], + ['failedLoginAttempts' => $blockAfterNth + 1, 'expected' => 9], + ['failedLoginAttempts' => $blockAfterNth + 2, 'expected' => 9], + ['failedLoginAttempts' => $blockAfterNth + 3, 'expected' => 9], + ['failedLoginAttempts' => $blockAfterNth + 4, 'expected' => 16], + ['failedLoginAttempts' => $blockAfterNth + 5, 'expected' => 25], + ['failedLoginAttempts' => $blockAfterNth + 6, 'expected' => 36], + ['failedLoginAttempts' => $blockAfterNth + 10, 'expected' => 100], + ['failedLoginAttempts' => $blockAfterNth + 20, 'expected' => 400], + ['failedLoginAttempts' => $blockAfterNth + 50, 'expected' => 2500], + ['failedLoginAttempts' => $blockAfterNth + 60, 'expected' => 3600], + ['failedLoginAttempts' => $blockAfterNth + 61, 'expected' => 3600], + ['failedLoginAttempts' => $blockAfterNth + 100, 'expected' => 3600], + ]; + foreach ($testCases as $testCase) { + + // Act: + $actual = Authenticator::calculateSecondsToDelay( + $testCase['failedLoginAttempts'] + ); + + // Assert: + $this->assertSame($testCase['expected'], $actual, sprintf( + 'Expected %s failed login attempts to result in %s second(s), not %s.', + var_export($testCase['failedLoginAttempts'], true), + var_export($testCase['expected'], true), + var_export($actual, true) + )); + } + } + + public function testGetSecondsUntilUnblocked() + { + // Arrange: + $testCases = [[ + 'numRecentFailures' => 0, + 'mostRecentFailureAt' => null, + 'expected' => 0, + ], [ + 'numRecentFailures' => Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN - 1, // no delay yet + 'mostRecentFailureAt' => UtcTime::format('-5 seconds'), + 'expected' => 0, + ], [ + 'numRecentFailures' => Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN + 5, // a 25-second delay + 'mostRecentFailureAt' => UtcTime::format('-5 seconds'), + 'expected' => 20, + ]]; + foreach ($testCases as $testCase) { + + // Act: + $actual = Authenticator::getSecondsUntilUnblocked( + $testCase['numRecentFailures'], + $testCase['mostRecentFailureAt'] + ); + + // Assert: + $this->assertSame($testCase['expected'], $actual); + } + } + + public function testIsEnoughFailedLoginsToBlock() + { + // Arrange: + $testCases = [ + ['expected' => false, 'failedLogins' => 0], + ['expected' => false, 'failedLogins' => Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN - 1], + ['expected' => true, 'failedLogins' => Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN], + ['expected' => true, 'failedLogins' => Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN + 1], + ]; + foreach ($testCases as $testCase) { + + // Act: + $actual = Authenticator::isEnoughFailedLoginsToBlock( + $testCase['failedLogins'] + ); + + // Assert: + $this->assertSame($testCase['expected'], $actual); + } + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummyFailedCaptcha.php b/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummyFailedCaptcha.php new file mode 100644 index 00000000..b6d5387c --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/captcha/DummyFailedCaptcha.php @@ -0,0 +1,13 @@ +assertTrue(is_array($sspConfig), sprintf( + 'Expected an array, got this: %s', + var_export($sspConfig, true) + )); + } + + public function testGetSspConfigFor() + { + // Arrange: + $category = 'mysql'; + + // Act: + $result = ConfigManager::getSspConfigFor($category); + + // Assert: + $this->assertArrayHasKey('database', $result, var_export($result, true)); + } + + public function testRemoveCategory() + { + // Arrange: + $testCases = [ + ['key' => null, 'expected' => null], + ['key' => '', 'expected' => ''], + ['key' => '.', 'expected' => ''], + ['key' => '.abc', 'expected' => 'abc'], + ['key' => 'category.subKey', 'expected' => 'subKey'], + ['key' => 'category.subCategory.subKey', 'expected' => 'subCategory.subKey'], + ]; + foreach ($testCases as $testCase) { + + // Act: + $actual = ConfigManager::removeCategory($testCase['key']); + + // Assert: + $this->assertSame($testCase['expected'], $actual, sprintf( + 'Expected removing the category from %s result in %s, not %s.', + var_export($testCase['key'], true), + var_export($testCase['expected'], true), + var_export($actual, true) + )); + } + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/csrf/CsrfProtectorTest.php b/modules/silauth/lib/Auth/Source/tests/unit/csrf/CsrfProtectorTest.php new file mode 100644 index 00000000..fc7e97d3 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/csrf/CsrfProtectorTest.php @@ -0,0 +1,26 @@ +getMasterToken(); + $firstTokenAgain = $csrfProtector->getMasterToken(); + + // Act: + $csrfProtector->changeMasterToken(); + $secondToken = $csrfProtector->getMasterToken(); + $secondTokenAgain = $csrfProtector->getMasterToken(); + + // Assert: + $this->assertSame($firstToken, $firstTokenAgain); + $this->assertNotEquals($firstToken, $secondToken); + $this->assertSame($secondToken, $secondTokenAgain); + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/csrf/FakeSession.php b/modules/silauth/lib/Auth/Source/tests/unit/csrf/FakeSession.php new file mode 100644 index 00000000..41d1fa48 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/csrf/FakeSession.php @@ -0,0 +1,41 @@ +inMemoryDataStore = []; + } + + /** + * @param string $type + * @param string|null $id + * @return mixed + */ + public function getData($type, $id) + { + return $this->inMemoryDataStore[$type][$id] ?? null; + } + + public static function getSessionFromRequest($sessionId = null) + { + return new self(); + } + + public function setData($type, $id, $data, $timeout = null) + { + // Make sure an array exists for that type of data. + $this->inMemoryDataStore[$type] = $this->inMemoryDataStore[$type] ?? []; + + // Store the given data. + $this->inMemoryDataStore[$type][$id] = $data; + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/http/DummyRequest.php b/modules/silauth/lib/Auth/Source/tests/unit/http/DummyRequest.php new file mode 100644 index 00000000..ae2cfbf7 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/http/DummyRequest.php @@ -0,0 +1,31 @@ +dummyIpAddress]; + } + + public function setDummyIpAddress($dummyIpAddress) + { + if ( ! self::isValidIpAddress($dummyIpAddress)) { + throw new \InvalidArgumentException(sprintf( + '%s is not a valid IP address.', + var_export($dummyIpAddress, true) + )); + } + + $this->dummyIpAddress = $dummyIpAddress; + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/http/RequestTest.php b/modules/silauth/lib/Auth/Source/tests/unit/http/RequestTest.php new file mode 100644 index 00000000..7db3f7bb --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/http/RequestTest.php @@ -0,0 +1,51 @@ + '11.11.11.11', + 'trustedIpAddresses' => $trustedIpAddresses, + 'expected' => true, + ], [ + 'ipAddress' => '22.22.22.22', + 'trustedIpAddresses' => $trustedIpAddresses, + 'expected' => false, + ], [ + 'ipAddress' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'trustedIpAddresses' => $trustedIpAddresses, + 'expected' => true, + ], [ + 'ipAddress' => '2001:0DB8:85A3:0000:0000:8A2E:0370:7334', + 'trustedIpAddresses' => $trustedIpAddresses, + 'expected' => true, + ], [ + 'ipAddress' => '2001:0db8:85a3:0000:0000:8a2e:0370:9999', + 'trustedIpAddresses' => $trustedIpAddresses, + 'expected' => false, + ]]; + foreach ($testCases as $testCase) { + $request = new Request($testCase['trustedIpAddresses']); + + // Act: + $actual = $request->isTrustedIpAddress($testCase['ipAddress']); + + // Assert: + $this->assertSame($testCase['expected'], $actual, sprintf( + 'Expected %s %sto be trusted.', + var_export($testCase['ipAddress'], true), + ($testCase['expected'] ? '' : 'not ') + )); + } + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginIpAddressTest.php b/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginIpAddressTest.php new file mode 100644 index 00000000..84f2163d --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginIpAddressTest.php @@ -0,0 +1,189 @@ +assertTrue($model->insert(false)); + } + } + + public function testCountRecentFailedLoginsFor() + { + // Arrange: + $ipAddress = '100.110.120.130'; + $fixtures = [[ + 'ip_address' => $ipAddress, + 'occurred_at_utc' => UtcTime::format('-61 minutes'), // Not recent. + ], [ + 'ip_address' => $ipAddress, + 'occurred_at_utc' => UtcTime::format('-59 minutes'), // Recent. + ], [ + 'ip_address' => $ipAddress, + 'occurred_at_utc' => UtcTime::format(), // Now (thus, recent). + ]]; + $this->setDbFixture($fixtures); + + // Pre-assert: + $this->assertCount( + count($fixtures), + FailedLoginIpAddress::getFailedLoginsFor($ipAddress) + ); + + // Act: + $result = FailedLoginIpAddress::countRecentFailedLoginsFor($ipAddress); + + // Assert: + $this->assertEquals(2, $result); + } + + public function testGetMostRecentFailedLoginFor() + { + // Arrange: + $ipAddress = '100.110.120.130'; + $nowDateTimeString = UtcTime::now(); + $fixtures = [[ + 'ip_address' => $ipAddress, + 'occurred_at_utc' => UtcTime::format('-61 minutes'), + ], [ + 'ip_address' => $ipAddress, + 'occurred_at_utc' => $nowDateTimeString, + ], [ + 'ip_address' => $ipAddress, + 'occurred_at_utc' => UtcTime::format('-59 minutes'), + ]]; + $this->setDbFixture($fixtures); + + // Act: + $fliaRecord = FailedLoginIpAddress::getMostRecentFailedLoginFor($ipAddress); + + // Assert: + $this->assertSame($nowDateTimeString, $fliaRecord->occurred_at_utc); + } + + public function testIsCaptchaRequiredFor() + { + // Arrange: + $captchaAfterNth = Authenticator::REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN; + $testCases = [[ + 'dbFixture' => array_fill( + 0, + $captchaAfterNth, + ['ip_address' => '11.11.11.11', 'occurred_at_utc' => UtcTime::now()] + ), + 'ipAddress' => '11.11.11.11', + 'expected' => true, + ], [ + 'dbFixture' => array_fill( + 0, + $captchaAfterNth - 1, + ['ip_address' => '22.22.22.22', 'occurred_at_utc' => UtcTime::now()] + ), + 'ipAddress' => '22.22.22.22', + 'expected' => false, + ]]; + foreach ($testCases as $testCase) { + $this->setDbFixture($testCase['dbFixture']); + + // Act: + $actual = FailedLoginIpAddress::isCaptchaRequiredFor($testCase['ipAddress']); + + // Assert: + $this->assertSame($testCase['expected'], $actual); + } + } + + public function testIsRateLimitBlocking() + { + // Arrange: + $blockAfterNth = Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN; + $testCases = [[ + 'dbFixture' => array_fill( + 0, + $blockAfterNth, + ['ip_address' => '11.11.11.11', 'occurred_at_utc' => UtcTime::now()] + ), + 'ipAddress' => '11.11.11.11', + 'expected' => true, + ], [ + 'dbFixture' => array_fill( + 0, + $blockAfterNth - 1, + ['ip_address' => '22.22.22.22', 'occurred_at_utc' => UtcTime::now()] + ), + 'ipAddress' => '22.22.22.22', + 'expected' => false, + ]]; + foreach ($testCases as $testCase) { + $this->setDbFixture($testCase['dbFixture']); + + // Act: + $actual = FailedLoginIpAddress::isRateLimitBlocking($testCase['ipAddress']); + + // Assert: + $this->assertSame($testCase['expected'], $actual); + } + } + + public function testRecordFailedLoginBy() + { + // Arrange: + $ipAddress = '101.102.103.104'; + $dbFixture = [ + ['ip_address' => $ipAddress, 'occurred_at_utc' => UtcTime::format()] + ]; + $this->setDbFixture($dbFixture); + $logger = new Psr3EchoLogger(); + $expectedPre = count($dbFixture); + $expectedPost = $expectedPre + 1; + + // Pre-assert: + $this->assertCount( + $expectedPre, + FailedLoginIpAddress::getFailedLoginsFor($ipAddress) + ); + + // Act: + FailedLoginIpAddress::recordFailedLoginBy([$ipAddress], $logger); + + // Assert: + $this->assertCount( + $expectedPost, + FailedLoginIpAddress::getFailedLoginsFor($ipAddress) + ); + } + + public function testResetFailedLoginsBy() + { + // Arrange: + $ipAddress = '101.102.103.104'; + $otherIpAddress = '201.202.203.204'; + $logger = new Psr3EchoLogger(); + FailedLoginIpAddress::deleteAll(); + FailedLoginIpAddress::recordFailedLoginBy( + [$ipAddress, $otherIpAddress], + $logger + ); + + // Pre-assert: + $this->assertCount(1, FailedLoginIpAddress::getFailedLoginsFor($ipAddress)); + $this->assertCount(1, FailedLoginIpAddress::getFailedLoginsFor($otherIpAddress)); + + // Act: + FailedLoginIpAddress::resetFailedLoginsBy([$ipAddress]); + + // Assert: + $this->assertCount(0, FailedLoginIpAddress::getFailedLoginsFor($ipAddress)); + $this->assertCount(1, FailedLoginIpAddress::getFailedLoginsFor($otherIpAddress)); + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginUsernameTest.php b/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginUsernameTest.php new file mode 100644 index 00000000..bd403eff --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/models/FailedLoginUsernameTest.php @@ -0,0 +1,188 @@ +assertTrue($model->insert(false)); + } + } + + public function testCountRecentFailedLoginsFor() + { + // Arrange: + $username = 'john_smith'; + $fixtures = [[ + 'username' => $username, + 'occurred_at_utc' => UtcTime::format('-61 minutes'), // Not recent. + ], [ + 'username' => $username, + 'occurred_at_utc' => UtcTime::format('-59 minutes'), // Recent. + ], [ + 'username' => $username, + 'occurred_at_utc' => UtcTime::format(), // Now (thus, recent). + ]]; + $this->setDbFixture($fixtures); + + // Pre-assert: + $this->assertCount( + count($fixtures), + FailedLoginUsername::getFailedLoginsFor($username) + ); + + // Act: + $result = FailedLoginUsername::countRecentFailedLoginsFor($username); + + // Assert: + $this->assertEquals(2, $result); + } + + public function testGetMostRecentFailedLoginFor() + { + // Arrange: + $username = 'dummy_username'; + $nowDateTimeString = UtcTime::now(); + $fixtures = [[ + 'username' => $username, + 'occurred_at_utc' => UtcTime::format('-61 minutes'), + ], [ + 'username' => $username, + 'occurred_at_utc' => $nowDateTimeString, + ], [ + 'username' => $username, + 'occurred_at_utc' => UtcTime::format('-59 minutes'), + ]]; + $this->setDbFixture($fixtures); + + // Act: + $fliaRecord = FailedLoginUsername::getMostRecentFailedLoginFor($username); + + // Assert: + $this->assertSame($nowDateTimeString, $fliaRecord->occurred_at_utc); + } + + public function testIsCaptchaRequiredFor() + { + // Arrange: + $captchaAfterNth = Authenticator::REQUIRE_CAPTCHA_AFTER_NTH_FAILED_LOGIN; + $testCases = [[ + 'dbFixture' => array_fill( + 0, + $captchaAfterNth, + ['username' => 'dummy_username', 'occurred_at_utc' => UtcTime::now()] + ), + 'username' => 'dummy_username', + 'expected' => true, + ], [ + 'dbFixture' => array_fill( + 0, + $captchaAfterNth - 1, + ['username' => 'dummy_other_username', 'occurred_at_utc' => UtcTime::now()] + ), + 'username' => 'dummy_other_username', + 'expected' => false, + ]]; + foreach ($testCases as $testCase) { + $this->setDbFixture($testCase['dbFixture']); + + // Act: + $actual = FailedLoginUsername::isCaptchaRequiredFor($testCase['username']); + + // Assert: + $this->assertSame($testCase['expected'], $actual); + } + } + + public function testIsRateLimitBlocking() + { + // Arrange: + $blockAfterNth = Authenticator::BLOCK_AFTER_NTH_FAILED_LOGIN; + $testCases = [[ + 'dbFixture' => array_fill( + 0, + $blockAfterNth, + ['username' => 'dummy_username', 'occurred_at_utc' => UtcTime::now()] + ), + 'username' => 'dummy_username', + 'expected' => true, + ], [ + 'dbFixture' => array_fill( + 0, + $blockAfterNth - 1, + ['username' => 'dummy_other_username', 'occurred_at_utc' => UtcTime::now()] + ), + 'username' => 'dummy_other_username', + 'expected' => false, + ]]; + foreach ($testCases as $testCase) { + $this->setDbFixture($testCase['dbFixture']); + + // Act: + $actual = FailedLoginUsername::isRateLimitBlocking($testCase['username']); + + // Assert: + $this->assertSame($testCase['expected'], $actual); + } + } + + public function testRecordFailedLoginBy() + { + // Arrange: + $username = 'dummy_username'; + $dbFixture = [ + ['username' => $username, 'occurred_at_utc' => UtcTime::format()] + ]; + $this->setDbFixture($dbFixture); + $logger = new Psr3EchoLogger(); + $expectedPre = count($dbFixture); + $expectedPost = $expectedPre + 1; + + // Pre-assert: + $this->assertCount( + $expectedPre, + FailedLoginUsername::getFailedLoginsFor($username) + ); + + // Act: + FailedLoginUsername::recordFailedLoginBy($username, $logger); + + // Assert: + $this->assertCount( + $expectedPost, + FailedLoginUsername::getFailedLoginsFor($username) + ); + } + + public function testResetFailedLoginsBy() + { + // Arrange: + $username = 'dummy_username'; + $otherUsername = 'dummy_other_username'; + $dbFixture = [ + ['username' => $username, 'occurred_at_utc' => UtcTime::format()], + ['username' => $otherUsername, 'occurred_at_utc' => UtcTime::format()], + ]; + $this->setDbFixture($dbFixture); + + // Pre-assert: + $this->assertCount(1, FailedLoginUsername::getFailedLoginsFor($username)); + $this->assertCount(1, FailedLoginUsername::getFailedLoginsFor($otherUsername)); + + // Act: + FailedLoginUsername::resetFailedLoginsBy($username); + + // Assert: + $this->assertCount(0, FailedLoginUsername::getFailedLoginsFor($username)); + $this->assertCount(1, FailedLoginUsername::getFailedLoginsFor($otherUsername)); + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/text/TextTest.php b/modules/silauth/lib/Auth/Source/tests/unit/text/TextTest.php new file mode 100644 index 00000000..027cbfc7 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/text/TextTest.php @@ -0,0 +1,42 @@ + '', 'expected' => ''], + ['input' => null, 'expected' => ''], + ['input' => false, 'expected' => ''], + ['input' => true, 'expected' => ''], + ['input' => 'null', 'expected' => 'null'], + ['input' => 'false', 'expected' => 'false'], + ['input' => 'true', 'expected' => 'true'], + ['input' => 'abc XYZ', 'expected' => 'abc XYZ'], + ['input' => ' leading space', 'expected' => 'leading space'], + ['input' => 'trailing space ', 'expected' => 'trailing space'], + ['input' => 'trailing space ', 'expected' => 'trailing space'], + ['input' => 'low ASCII char: ' . chr(2), 'expected' => 'low ASCII char:'], + ['input' => 'high ASCII char: ' . chr(160), 'expected' => 'high ASCII char: ' . chr(160)], + ['input' => 'with `backticks`', 'expected' => 'with backticks'], + ]; + foreach ($testCases as $testCase) { + + // Act: + $actual = Text::sanitizeString($testCase['input']); + + // Assert: + $this->assertSame($testCase['expected'], $actual, sprintf( + 'Expected sanitizing %s to result in %s, not %s.', + var_export($testCase['input'], true), + var_export($testCase['expected'], true), + var_export($actual, true) + )); + } + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/time/UtcTimeTest.php b/modules/silauth/lib/Auth/Source/tests/unit/time/UtcTimeTest.php new file mode 100644 index 00000000..5a55c13f --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/time/UtcTimeTest.php @@ -0,0 +1,167 @@ + '1 Jan 2000 00:00:00 -0000', + 'expected' => '2000-01-01 00:00:00', + ], [ + 'dateTimeString' => '2016-Dec-25 12:00pm', + 'expected' => '2016-12-25 12:00:00', + ], + ]; + foreach ($testCases as $testCase) { + + // Act: + $actual = UtcTime::format($testCase['dateTimeString']); + + // Assert: + $this->assertSame($testCase['expected'], $actual); + } + } + + public function testGetRemainingSeconds() + { + // Arrange: + $testCases = [ + ['total' => 1, 'elapsed' => null, 'expectException' => '\TypeError'], + ['total' => null, 'elapsed' => 1, 'expectException' => '\TypeError'], + ['total' => 1, 'elapsed' => '1', 'expectException' => '\TypeError'], + ['total' => '1', 'elapsed' => 1, 'expectException' => '\TypeError'], + ['total' => -1, 'elapsed' => 1, 'expected' => 0], + ['total' => -1, 'elapsed' => 0, 'expected' => 0], + ['total' => 0, 'elapsed' => 0, 'expected' => 0], + ['total' => 0, 'elapsed' => 5, 'expected' => 0], + ['total' => 5, 'elapsed' => 0, 'expected' => 5], + ['total' => 5, 'elapsed' => 5, 'expected' => 0], + ['total' => 8, 'elapsed' => 5, 'expected' => 3], + ['total' => 60, 'elapsed' => 45, 'expected' => 15], + ]; + foreach ($testCases as $testCase) { + $total = $testCase['total']; + $elapsed = $testCase['elapsed']; + $expected = $testCase['expected'] ?? null; + $expectException = $testCase['expectException'] ?? null; + + // Pre-assert: + if ($expectException !== null) { + $this->expectException($expectException); + } + + // Act: + $actual = UtcTime::getRemainingSeconds($total, $elapsed); + + // Assert: + if ($expectException !== null) { + $this->fail(sprintf( + 'Expected a %s to be thrown for (total: %s, elapsed: %s).', + $expectException, + var_export($total, true), + var_export($elapsed, true) + )); + } + $this->assertSame($expected, $actual, sprintf( + 'Expected (total: %s, elapsed: %s) to result in %s, not %s.', + var_export($total, true), + var_export($elapsed, true), + var_export($expected, true), + var_export($actual, true) + )); + } + } + + public function testGetSecondsSinceDateTime() + { + // Arrange: + $testCases = [ + ['value' => '1970-01-01 00:00:00', 'expected' => time()], + ['value' => UtcTime::format(), 'expected' => 0], + ['value' => UtcTime::format('-10 seconds'), 'expected' => 10], + ['value' => UtcTime::format('-2 hours'), 'expected' => 7200], + ]; + foreach ($testCases as $testCase) { + + // Act: + $actual = UtcTime::getSecondsSinceDateTime($testCase['value']); + + // Assert: + $this->assertEqualsWithDelta( + $testCase['expected'], + $actual, + 1, + sprintf('Expected %s to result in %s, not %s.', + var_export($testCase['value'], true), + var_export($testCase['expected'], true), + var_export($actual, true) + ) + ); + } + } + + public function testGetSecondsSinceDateTimeEmptyString() + { + $this->expectException('\InvalidArgumentException'); + UtcTime::getSecondsSinceDateTime(''); + } + + public function testGetSecondsSinceDateTimeInvalidDateTimeString() + { + $this->expectException('\Exception'); + UtcTime::getSecondsSinceDateTime('asdf'); + } + + public function testGetSecondsSinceDateTimeNull() + { + $this->expectException('\TypeError'); + UtcTime::getSecondsSinceDateTime(null); + } + + public function testGetSecondsUntil() + { + // Arrange: + $dayOneString = 'Tue, 13 Dec 2016 00:00:00 -0500'; + $dayTwoString = 'Wed, 14 Dec 2016 00:00:00 -0500'; + $expected = 86400; // 86400 = seconds in a day + $dayOneUtcTime = new UtcTime($dayOneString); + $dayTwoUtcTime = new UtcTime($dayTwoString); + + // Act: + $actual = $dayOneUtcTime->getSecondsUntil($dayTwoUtcTime); + + // Assert: + $this->assertSame($expected, $actual); + } + + public function testGetTimestamp() + { + // Arrange: + $timestamp = time(); + $utcTime = new UtcTime(date('r', $timestamp)); + + // Act: + $result = $utcTime->getTimestamp(); + + // Assert: + $this->assertSame($timestamp, $result); + } + + public function testNow() + { + // Arrange: + $expected = gmdate(UtcTime::DATE_TIME_FORMAT, time()); + + // Act: + $actual = UtcTime::now(); + + // Assert: + $this->assertSame($expected, $actual); + } +} diff --git a/modules/silauth/lib/Auth/Source/tests/unit/time/WaitTimeTest.php b/modules/silauth/lib/Auth/Source/tests/unit/time/WaitTimeTest.php new file mode 100644 index 00000000..b2e5b2c5 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/tests/unit/time/WaitTimeTest.php @@ -0,0 +1,73 @@ + 0, 'expected' => '5 seconds'], + ['secondsToWait' => 1, 'expected' => '5 seconds'], + ['secondsToWait' => 5, 'expected' => '5 seconds'], + ['secondsToWait' => 6, 'expected' => '10 seconds'], + ['secondsToWait' => 17, 'expected' => '20 seconds'], + ['secondsToWait' => 22, 'expected' => '30 seconds'], + ['secondsToWait' => 41, 'expected' => '1 minute'], + ['secondsToWait' => 90, 'expected' => '2 minutes'], + ['secondsToWait' => 119, 'expected' => '2 minutes'], + ['secondsToWait' => 120, 'expected' => '2 minutes'], + ['secondsToWait' => 121, 'expected' => '3 minutes'], + ]; + foreach ($testCases as $testCase) { + $waitTime = new WaitTime($testCase['secondsToWait']); + + // Act: + $actual = (string)$waitTime; + + // Assert: + $this->assertSame($testCase['expected'], $actual, sprintf( + 'Expected %s second(s) to result in %s, not %s.', + var_export($testCase['secondsToWait'], true), + var_export($testCase['expected'], true), + var_export($actual, true) + )); + } + } + + public function testGetLongestWaitTime() + { + // Arrange: + $testCases = [ + ['durations' => [], 'expectException' => '\InvalidArgumentException'], + ['durations' => [0, 0], 'expected' => new WaitTime(0)], + ['durations' => [0, 1], 'expected' => new WaitTime(1)], + ['durations' => [1, 0], 'expected' => new WaitTime(1)], + ['durations' => [6], 'expected' => new WaitTime(6)], + ['durations' => [5, 5, 6], 'expected' => new WaitTime(6)], + ['durations' => [5, 6, 5], 'expected' => new WaitTime(6)], + ['durations' => [6, 5, 5], 'expected' => new WaitTime(6)], + ['durations' => [0, 17], 'expected' => new WaitTime(17)], + ['durations' => [17, 5], 'expected' => new WaitTime(17)], + ]; + foreach ($testCases as $testCase) { + if (array_key_exists('expectException', $testCase)) { + $this->expectException($testCase['expectException']); + } + + // Act: + $actual = WaitTime::getLongestWaitTime($testCase['durations']); + + // Assert: + $this->assertEquals($testCase['expected'], $actual, sprintf( + 'Expected the longest of %s second(s) to be a wait time of %s, not %s.', + json_encode($testCase['durations']), + $testCase['expected'], + $actual + )); + } + } +} diff --git a/modules/silauth/lib/Auth/Source/text/Text.php b/modules/silauth/lib/Auth/Source/text/Text.php new file mode 100644 index 00000000..eed1282c --- /dev/null +++ b/modules/silauth/lib/Auth/Source/text/Text.php @@ -0,0 +1,34 @@ + FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_BACKTICK, + ]); + return trim($output); + } + + /** + * See if the given string (haystack) starts with the given prefix (needle). + * + * @param string $haystack The string to search. + * @param string $needle The string to search for. + * @return boolean + */ + public static function startsWith(string $haystack, string $needle) + { + $length = mb_strlen($needle); + return (mb_substr($haystack, 0, $length) === $needle); + } +} diff --git a/modules/silauth/lib/Auth/Source/time/UtcTime.php b/modules/silauth/lib/Auth/Source/time/UtcTime.php new file mode 100644 index 00000000..af727882 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/time/UtcTime.php @@ -0,0 +1,120 @@ +. + * @throws Exception If an invalid date/time string is provided, an + * \Exception will be thrown. + */ + public function __construct(string $dateTimeString = 'now') + { + $this->utc = new \DateTimeZone('UTC'); + $this->dateTime = new \DateTime($dateTimeString, $this->utc); + } + + public function __toString() + { + return $this->dateTime->format(self::DATE_TIME_FORMAT); + } + + /** + * Convert the given date/time description to a formatted date/time string + * in the UTC time zone. + * + * @param string $dateTimeString (Optional:) The date/time to use. If not + * given, 'now' will be used. + * @return string + * @throws Exception If an invalid date/time string is provided, an + * \Exception will be thrown. + */ + public static function format(string $dateTimeString = 'now') + { + return (string)(new UtcTime($dateTimeString)); + } + + /** + * Given a total number of seconds and an elapsed number of seconds, get the + * remaining seconds until that total has passed. If the total has already + * passed (i.e. if elapsed is equal to or greater than total), zero will be + * returned. + * + * @param int $totalSeconds The total number of seconds. + * @param int $elapsedSeconds The number of seconds that have already + * passed. + * @return int The number of seconds remaining. + */ + public static function getRemainingSeconds(int $totalSeconds, int $elapsedSeconds) + { + $remainingSeconds = $totalSeconds - $elapsedSeconds; + return max($remainingSeconds, 0); + } + + /** + * Get the number of seconds we have to go back to get from this UTC time to + * the given UTC time. A positive number will be returned if the given UTC + * time occurred before this UTC time. If they are the same, zero will be + * returned. Otherwise, a negative number will be returned. + * + * @param \SimpleSAML\Module\silauth\Auth\Source\time\UtcTime $otherUtcTime The other UTC time + * (presumably in the past, though not necessarily). + * @return int The number of seconds + */ + public function getSecondsSince(UtcTime $otherUtcTime) + { + return $this->getTimestamp() - $otherUtcTime->getTimestamp(); + } + + /** + * Get the number of seconds since the given date/time string. + * + * @param string $dateTimeString A date/time string. + * @return int The number of seconds that have elapsed since that date/time. + * @throws Exception If an invalid date/time string is provided, an + * \Exception will be thrown. + * @throws \InvalidArgumentException + */ + public static function getSecondsSinceDateTime(string $dateTimeString) + { + if (empty($dateTimeString)) { + throw new \InvalidArgumentException(sprintf( + 'The given value (%s) is not a date/time string.', + var_export($dateTimeString, true) + )); + } + $nowUtc = new UtcTime(); + $dateTimeUtc = new UtcTime($dateTimeString); + return $nowUtc->getSecondsSince($dateTimeUtc); + } + + public function getSecondsUntil(UtcTime $otherUtcTime) + { + return $otherUtcTime->getTimestamp() - $this->getTimestamp(); + } + + public function getTimestamp() + { + return $this->dateTime->getTimestamp(); + } + + /** + * Get the current date/time (UTC) as a formatted string + * + * @return string + */ + public static function now() + { + return self::format('now'); + } +} diff --git a/modules/silauth/lib/Auth/Source/time/WaitTime.php b/modules/silauth/lib/Auth/Source/time/WaitTime.php new file mode 100644 index 00000000..ba5094f3 --- /dev/null +++ b/modules/silauth/lib/Auth/Source/time/WaitTime.php @@ -0,0 +1,73 @@ +friendlyNumber = 5; + $this->unit = self::UNIT_SECOND; + } elseif ($secondsToWait <= 30) { + $this->friendlyNumber = (int)ceil($secondsToWait / 10) * 10; + $this->unit = self::UNIT_SECOND; + } else { + $this->friendlyNumber = (int)ceil($secondsToWait / 60); + $this->unit = self::UNIT_MINUTE; + } + } + + public function getFriendlyNumber() + { + return $this->friendlyNumber; + } + + /** + * Get a WaitTime representing the longest of the given durations. + * + * @param int[] $durationsInSeconds A list of (at least one) duration(s), in + * seconds. + * @return WaitTime + */ + public static function getLongestWaitTime(array $durationsInSeconds) + { + if (empty($durationsInSeconds)) { + throw new \InvalidArgumentException('No durations given.', 1487605801); + } + return new WaitTime(max($durationsInSeconds)); + } + + public function getUnit() + { + return $this->unit; + } + + public function __toString() + { + return sprintf( + '%s %s%s', + $this->friendlyNumber, + $this->unit, + (($this->friendlyNumber === 1) ? '' : 's') + ); + } +} diff --git a/modules/silauth/lib/Auth/Source/traits/LoggerAwareTrait.php b/modules/silauth/lib/Auth/Source/traits/LoggerAwareTrait.php new file mode 100644 index 00000000..65fabe5a --- /dev/null +++ b/modules/silauth/lib/Auth/Source/traits/LoggerAwareTrait.php @@ -0,0 +1,29 @@ +logger)) { + $this->logger = new NullLogger(); + } + } + + /** + * Set a logger for this class instance to use. + * + * @param LoggerInterface $logger A PSR-3 compliant logger. + * @return null + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } +} diff --git a/modules/silauth/lib/Auth/Source/yii b/modules/silauth/lib/Auth/Source/yii new file mode 100755 index 00000000..a163c10e --- /dev/null +++ b/modules/silauth/lib/Auth/Source/yii @@ -0,0 +1,43 @@ +#!/usr/bin/env php + ['db' => [ + 'dsn' => sprintf( + 'mysql:host=%s;dbname=%s', + Env::get('MYSQL_HOST'), + Env::get('MYSQL_DATABASE') + ), + 'username' => Env::get('MYSQL_USER'), + 'password' => Env::get('MYSQL_PASSWORD'), +]]]); +$exitCode = $application->run(); +exit($exitCode); diff --git a/modules/silauth/www/loginuserpass.php b/modules/silauth/www/loginuserpass.php new file mode 100644 index 00000000..5a34a285 --- /dev/null +++ b/modules/silauth/www/loginuserpass.php @@ -0,0 +1,108 @@ +getConfig('authsources.php'); +$silAuthConfig = $authSourcesConfig->getConfigItem('silauth'); + +$recaptchaSiteKey = $silAuthConfig->getString('recaptcha.siteKey', null); + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + try { + + $logger = new Psr3StdOutLogger(); + $csrfFromRequest = Request::sanitizeInputString(INPUT_POST, 'csrf-token'); + if ($csrfProtector->isTokenCorrect($csrfFromRequest)) { + + $username = Request::sanitizeInputString(INPUT_POST, 'username'); + $password = Request::getRawInputString(INPUT_POST, 'password'); + + SilAuth::handleLogin( + $authStateId, + $username, + $password + ); + } else { + $logger->error(json_encode([ + 'event' => 'Failed CSRF', + 'username' => Request::sanitizeInputString(INPUT_POST, 'username'), + 'userAgent' => Request::getUserAgent(), + ])); + } + + } catch (SimpleSAMLError $e) { + /* Login failed. Extract error code and parameters, to display the error. */ + $errorCode = $e->getErrorCode(); + $errorParams = $e->getParameters(); + } + + $csrfProtector->changeMasterToken(); +} + +$t = new Template($globalConfig, 'core:loginuserpass.php'); +$t->data['stateparams'] = array('AuthState' => $authStateId); +$t->data['username'] = $username; +$t->data['forceUsername'] = false; +$t->data['rememberUsernameEnabled'] = false; +$t->data['rememberMeEnabled'] = false; +$t->data['errorcode'] = $errorCode; +$t->data['errorparams'] = $errorParams; +$t->data['csrfToken'] = $csrfProtector->getMasterToken(); +$t->data['profileUrl'] = $state['templateData']['profileUrl'] ?? ''; +$t->data['helpCenterUrl'] = $state['templateData']['helpCenterUrl'] ?? ''; + +/* For simplicity's sake, don't bother telling this Request to trust any IP + * addresses. This is okay because we only track the failures of untrusted + * IP addresses, so there will be no failed logins of IP addresses we trust. */ +$request = new Request(); +if (Authenticator::isCaptchaRequired($username, $request->getUntrustedIpAddresses())) { + $t->data['recaptcha.siteKey'] = $recaptchaSiteKey; +} + +if (isset($state['SPMetadata'])) { + $t->data['SPMetadata'] = $state['SPMetadata']; +} else { + $t->data['SPMetadata'] = null; +} + +$t->show(); +exit(); diff --git a/modules/silauth/www/status.php b/modules/silauth/www/status.php new file mode 100644 index 00000000..d062a805 --- /dev/null +++ b/modules/silauth/www/status.php @@ -0,0 +1,32 @@ + ['db' => [ + 'dsn' => sprintf( + 'mysql:host=%s;dbname=%s', + Env::get('MYSQL_HOST'), + Env::get('MYSQL_DATABASE') + ), + 'username' => Env::get('MYSQL_USER'), + 'password' => Env::get('MYSQL_PASSWORD'), + ]]]); + $logger = new Psr3StdOutLogger(); + $system = new System($logger); + $system->reportStatus(); + +} catch (Throwable $t) { + + echo sprintf( + '%s (%s)', + $t->getMessage(), + $t->getCode() + ); + \http_response_code(500); +} diff --git a/modules/sildisco/lib/Auth/Process/AddIdp2NameId.php b/modules/sildisco/lib/Auth/Process/AddIdp2NameId.php new file mode 100644 index 00000000..0f816211 --- /dev/null +++ b/modules/sildisco/lib/Auth/Process/AddIdp2NameId.php @@ -0,0 +1,175 @@ + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + * + */ +class AddIdp2NameId extends \SimpleSAML\Auth\ProcessingFilter { + + const IDP_KEY = "saml:sp:IdP"; // the key that points to the entity id in the state + + // the metadata key for the IDP's Namespace code (i.e. short name) + const IDP_CODE_KEY = 'IDPNamespace'; + + const DELIMITER = '@'; // The symbol between the NameID proper and the Idp code. + + const SP_NAMEID_ATTR = 'saml:sp:NameID'; // The key for the NameID + + const VALUE_KEY = 'Value'; // The value key for the NamedID entry + + const ERROR_PREFIX = "AddIdp2NameId: "; // Text to go at the beginning of error messages + + const FORMAT_KEY = 'Format'; + + /** + * What NameQualifier should be used. + * Can be one of: + * - a string: The qualifier to use. + * - FALSE: Do not include a NameQualifier. This is the default. + * - TRUE: Use the IdP entity ID. + * + * @var string|bool + */ + private $nameQualifier; + + + /** + * What SPNameQualifier should be used. + * Can be one of: + * - a string: The qualifier to use. + * - FALSE: Do not include a SPNameQualifier. + * - TRUE: Use the SP entity ID. This is the default. + * + * @var string|bool + */ + private $spNameQualifier; + + + /** + * The format of this NameID. + * + * This property must be initialized the subclass. + * + * @var string + */ + protected $format; + + + /** + * Initialize this filter, parse configuration. + * + * @param array $config Configuration information about this filter. + * @param mixed $reserved For future use. + */ + public function __construct($config, $reserved) { + parent::__construct($config, $reserved); + assert('is_array($config)'); + + if (isset($config['NameQualifier'])) { + $this->nameQualifier = $config['NameQualifier']; + } else { + $this->nameQualifier = false; + } + + if (isset($config['SPNameQualifier'])) { + $this->spNameQualifier = $config['SPNameQualifier']; + } else { + $this->spNameQualifier = true; + } + + $this->format = Null; + if ( ! empty($config[self::FORMAT_KEY])) { + $this->format = (string) $config[self::FORMAT_KEY]; + } + } + + /** + * @param $nameId \SAML2\XML\saml\NameID + * @param $IDPNamespace string + * + * Modifies the nameID object by adding text to the end of its value attribute + */ + public function appendIdp($nameId, $IDPNamespace) { + + $suffix = self::DELIMITER . $IDPNamespace; + $value = $nameId->getValue(); + $nameId->setValue($value . $suffix); + return; + } + + + /** + * Apply filter to copy attributes. + * + * @param array &$state The current state array + */ + public function process(&$state) { + assert('is_array($state)'); + + $samlIDP = $state[self::IDP_KEY]; + + if (empty($state[self::SP_NAMEID_ATTR])) { + \SimpleSAML\Logger::warning( + self::SP_NAMEID_ATTR . ' attribute not available from ' . + $samlIDP . '.' + ); + return; + } + + // Get the potential IDPs from idp remote metadata + $metadataPath = __DIR__ . '/../../../../../metadata'; + + // If a unit test sends a different metadataPath, use it + if (isset($state['metadataPath'])) { + $metadataPath = $state['metadataPath']; + } + $idpEntries = \Sil\SspUtils\Metadata::getIdpMetadataEntries($metadataPath); + + $idpEntry = $idpEntries[$samlIDP]; + + // The IDP metadata must have an IDPNamespace entry + if ( ! isset($idpEntry[self::IDP_CODE_KEY])) { + throw new \SimpleSAML\Error\Exception(self::ERROR_PREFIX . "Missing required metadata entry: " . + self::IDP_CODE_KEY . "."); + } + + // IDPNamespace must be a non-empty string + if ( ! is_string($idpEntry[self::IDP_CODE_KEY])) { + throw new \SimpleSAML\Error\Exception(self::ERROR_PREFIX . "Required metadata " . + "entry, " . self::IDP_CODE_KEY . ", must be a non-empty string."); + } + + // IDPNamespace must not have special characters in it + if ( ! preg_match("/^[A-Za-z0-9_-]+$/", $idpEntry[self::IDP_CODE_KEY])) { + throw new \SimpleSAML\Error\Exception(self::ERROR_PREFIX . "Required metadata " . + "entry, " . self::IDP_CODE_KEY . ", must not be empty or contain anything except " . + "letters, numbers, hyphens and underscores."); + } + + $IDPNamespace = $idpEntry[self::IDP_CODE_KEY]; + + $nameId = $state[self::SP_NAMEID_ATTR]; + self::appendIdp($nameId, $IDPNamespace); + + $format = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'; + + if ( ! empty($this->format)) { + $format = $this->format; + } elseif ( ! empty($nameId->Format)) { + $format = $nameId->Format; + } + + $state['saml:NameID'][$format] = $nameId; + + } + +} diff --git a/modules/sildisco/lib/Auth/Process/LogUser.php b/modules/sildisco/lib/Auth/Process/LogUser.php new file mode 100644 index 00000000..c1cc2a55 --- /dev/null +++ b/modules/sildisco/lib/Auth/Process/LogUser.php @@ -0,0 +1,233 @@ +dynamoEndpoint = $config[self::DYNAMO_ENDPOINT_KEY] ?? null; + $this->dynamoRegion = $config[self::DYNAMO_REGION_KEY] ?? null; + $this->dynamoLogTable = $config[self::DYNAMO_LOG_TABLE_KEY] ?? null; + } + + /** + * Log info for a user's login to Dyanmodb + * + * @param array &$state The current state array + */ + public function process(&$state) { + if (! $this->configsAreValid()) { + return; + } + + $awsKey = getenv(self::AWS_ACCESS_KEY_ID_ENV); + if (! $awsKey ) { + \SimpleSAML\Logger::error(self::AWS_ACCESS_KEY_ID_ENV . " environment variable is required for LogUser."); + return; + } + $awsSecret = getenv(self::AWS_SECRET_ACCESS_KEY_ENV); + if (! $awsSecret ) { + \SimpleSAML\Logger::error(self::AWS_SECRET_ACCESS_KEY_ENV . " environment variable is required for LogUser."); + return; + } + + assert(is_array($state)); + + // Get the SP's entity id + $spEntityId = "SP entity ID not available"; + if (! empty($state['saml:sp:State']['SPMetadata']['entityid'])) { + $spEntityId = $state['saml:sp:State']['SPMetadata']['entityid']; + } + + $sdkConfig = [ + 'region' => $this->dynamoRegion, + 'version' => 'latest', + 'credentials' => [ + 'key' => $awsKey, + 'secret' => $awsSecret, + ], + ]; + + if (!empty($this->dynamoEndpoint)) { + $sdkConfig['endpoint'] = $this->dynamoEndpoint; + } + + $sdk = new \Aws\Sdk($sdkConfig); + + $dynamodb = $sdk->createDynamoDb(); + $marshaler = new Marshaler(); + + $userAttributes = $this->getUserAttributes($state); + + $logContents = array_merge( + $userAttributes, + [ + "ID" => uniqid(), + "IDP" => $this->getIdp($state), + "SP" => $spEntityId, + "Time" => date("Y-m-d H:i:s"), + "ExpiresAt" => time() + self::SECONDS_PER_YEAR, + ] + ); + + $logJson = json_encode($logContents); + + $item = $marshaler->marshalJson($logJson); + + $params = [ + 'TableName' => $this->dynamoLogTable, + 'Item' => $item, + ]; + + try { + $result = $dynamodb->putItem($params); + } catch (\Exception $e) { + \SimpleSAML\Logger::error("Unable to add item: ". $e->getMessage()); + } + } + + private function configsAreValid() { + $msg = ' config value not provided to LogUser.'; + + if (empty($this->dynamoRegion)) { + \SimpleSAML\Logger::error(self::DYNAMO_REGION_KEY . $msg); + return false; + } + + if (empty($this->dynamoLogTable)) { + \SimpleSAML\Logger::error(self::DYNAMO_LOG_TABLE_KEY . $msg); + return false; + } + + return true; + } + + private function getIdp(&$state) { + if (empty($state[self::IDP_KEY])) { + return 'No IDP available'; + } + + $samlIDP = $state[self::IDP_KEY]; + + // Get the potential IDPs from idp remote metadata + $metadataPath = __DIR__ . '/../../../../../metadata'; + + // If a unit test sends a different metadataPath, use it + if (isset($state['metadataPath'])) { + $metadataPath = $state['metadataPath']; + } + $idpEntries = \Sil\SspUtils\Metadata::getIdpMetadataEntries($metadataPath); + + // Get the IDPNamespace or else just use the IDP's entity ID + $idpEntry = $idpEntries[$samlIDP]; + + // If the IDPNamespace entry is a string, use it + if (isset($idpEntry[self::IDP_CODE_KEY]) && is_string($idpEntry[self::IDP_CODE_KEY])) { + return $idpEntry[self::IDP_CODE_KEY]; + } + + // Default, use the idp's entity ID + return $samlIDP; + } + + // Get the current user's common name attribute and/or eduPersonPrincipalName and/or employeeNumber + private function getUserAttributes($state) { + $attributes = $state['Attributes']; + + $cn = $this->getAttributeFrom($attributes, 'urn:oid:2.5.4.3', 'cn'); + + $eduPersonPrincipalName = $this->getAttributeFrom( + $attributes, + 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6', + 'eduPersonPrincipalName' + ); + + $employeeNumber = $this->getAttributeFrom( + $attributes, + 'urn:oid:2.16.840.1.113730.3.1.3', + 'employeeNumber' + ); + + $userAttrs = []; + + $userAttrs = $this->addUserAttribute($userAttrs, "CN", $cn); + $userAttrs = $this->addUserAttribute($userAttrs, "EduPersonPrincipalName", $eduPersonPrincipalName); + $userAttrs = $this->addUserAttribute($userAttrs, "EmployeeNumber", $employeeNumber); + + return $userAttrs; + } + + private function getAttributeFrom($attributes, $oidKey, $friendlyKey) { + if (!empty($attributes[$oidKey])) { + return $attributes[$oidKey][0]; + } + + if (!empty($attributes[$friendlyKey])) { + return $attributes[$friendlyKey][0]; + } + + return ''; + } + + // Dynamodb seems to complain when a value is an empty string. + // This ensures that only attributes with a non empty value get included. + private function addUserAttribute($attributes, $attrKey, $attr) { + if (!empty($attr)) { + $attributes[$attrKey] = $attr; + } + + return $attributes; + } + +} diff --git a/modules/sildisco/lib/Auth/Process/TagGroup.php b/modules/sildisco/lib/Auth/Process/TagGroup.php new file mode 100644 index 00000000..59402907 --- /dev/null +++ b/modules/sildisco/lib/Auth/Process/TagGroup.php @@ -0,0 +1,90 @@ +getData($sessionDataType, $sessionKey); + if ( ! $sessionValue) { + $sessionValue = []; + } + + // Will we need to wrap the idp in htmlspecialchars() + $authIdps = $session->getAuthData("hub-discovery", "saml:AuthenticatingAuthority"); + + if ( ! in_array($authIdps[0], $sessionValue)) { + $sessionValue[$authIdps[0]] = $authIdps[0]; + } + + $session->setData($sessionDataType, $sessionKey, $sessionValue); + } + + +} diff --git a/modules/sildisco/lib/Auth/Source/SP.php b/modules/sildisco/lib/Auth/Source/SP.php new file mode 100644 index 00000000..b9781797 --- /dev/null +++ b/modules/sildisco/lib/Auth/Source/SP.php @@ -0,0 +1,1240 @@ +getMetadataURL(); + } + + /* For compatibility with code that assumes that $metadata->getString('entityid') + * gives the entity id. */ + $config['entityid'] = $config['entityID']; + + $this->metadata = Configuration::loadFromArray( + $config, + 'authsources[' . var_export($this->authId, true) . ']' + ); + $this->entityId = $this->metadata->getString('entityID'); + $this->idp = $this->metadata->getString('idp', null); + $this->discoURL = $this->metadata->getString('discoURL', null); + $this->disable_scoping = $this->metadata->getBoolean('disable_scoping', false); + + if (empty($this->discoURL) && Module::isModuleEnabled('discojuice')) { + $this->discoURL = Module::getModuleURL('discojuice/central.php'); + } + } + + + /** + * Retrieve the URL to the metadata of this SP. + * + * @return string The metadata URL. + */ + public function getMetadataURL() + { + return Module::getModuleURL('saml/sp/metadata.php/' . urlencode($this->authId)); + } + + + /** + * Retrieve the entity id of this SP. + * + * @return string The entity id of this SP. + */ + public function getEntityId() + { + return $this->entityId; + } + + + /** + * Retrieve the metadata array of this SP, as a remote IdP would see it. + * + * @return array The metadata array for its use by a remote IdP. + */ + public function getHostedMetadata() + { + $entityid = $this->getEntityId(); + $metadata = [ + 'entityid' => $entityid, + 'metadata-set' => 'saml20-sp-remote', + 'SingleLogoutService' => $this->getSLOEndpoints(), + 'AssertionConsumerService' => $this->getACSEndpoints(), + ]; + + // add NameIDPolicy + if ($this->metadata->hasValue('NameIDPolicy')) { + $format = $this->metadata->getValue('NameIDPolicy'); + if (is_array($format)) { + $metadata['NameIDFormat'] = Configuration::loadFromArray($format)->getString( + 'Format', + Constants::NAMEID_TRANSIENT + ); + } elseif (is_string($format)) { + $metadata['NameIDFormat'] = $format; + } + } + + // add attributes + $name = $this->metadata->getLocalizedString('name', null); + $attributes = $this->metadata->getArray('attributes', []); + if ($name !== null) { + if (!empty($attributes)) { + $metadata['name'] = $name; + $metadata['attributes'] = $attributes; + if ($this->metadata->hasValue('attributes.required')) { + $metadata['attributes.required'] = $this->metadata->getArray('attributes.required'); + } + if ($this->metadata->hasValue('description')) { + $metadata['description'] = $this->metadata->getArray('description'); + } + if ($this->metadata->hasValue('attributes.NameFormat')) { + $metadata['attributes.NameFormat'] = $this->metadata->getString('attributes.NameFormat'); + } + if ($this->metadata->hasValue('attributes.index')) { + $metadata['attributes.index'] = $this->metadata->getInteger('attributes.index'); + } + if ($this->metadata->hasValue('attributes.isDefault')) { + $metadata['attributes.isDefault'] = $this->metadata->getBoolean('attributes.isDefault'); + } + } + } + + // add organization info + $org = $this->metadata->getLocalizedString('OrganizationName', null); + if ($org !== null) { + $metadata['OrganizationName'] = $org; + $metadata['OrganizationDisplayName'] = $this->metadata->getLocalizedString('OrganizationDisplayName', $org); + $metadata['OrganizationURL'] = $this->metadata->getLocalizedString('OrganizationURL', null); + if ($metadata['OrganizationURL'] === null) { + throw new Error\Exception( + 'If OrganizationName is set, OrganizationURL must also be set.' + ); + } + } + + // add contacts + $contacts = $this->metadata->getArray('contacts', []); + foreach ($contacts as $contact) { + $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); + } + + // add technical contact + $globalConfig = Configuration::getInstance(); + $email = $globalConfig->getString('technicalcontact_email', 'na@example.org'); + if ($email && $email !== 'na@example.org') { + $contact = [ + 'emailAddress' => $email, + 'name' => $globalConfig->getString('technicalcontact_name', null), + 'contactType' => 'technical', + ]; + $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); + } + + // add certificate(s) + $certInfo = Utils\Crypto::loadPublicKey($this->metadata, false, 'new_'); + $hasNewCert = false; + if ($certInfo !== null && array_key_exists('certData', $certInfo)) { + $hasNewCert = true; + $metadata['keys'][] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => true, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => 'new_', + 'url' => Module::getModuleURL( + 'admin/federation/cert', + [ + 'set' => 'saml20-sp-hosted', + 'source' => $this->getAuthId(), + 'prefix' => 'new_' + ] + ), + 'name' => 'sp', + ]; + } + + $certInfo = Utils\Crypto::loadPublicKey($this->metadata); + if ($certInfo !== null && array_key_exists('certData', $certInfo)) { + $metadata['keys'][] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => $hasNewCert ? false : true, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => '', + 'url' => Module::getModuleURL( + 'admin/federation/cert', + [ + 'set' => 'saml20-sp-hosted', + 'source' => $this->getAuthId(), + 'prefix' => '' + ] + ), + 'name' => 'sp', + ]; + } + + // add EntityAttributes extension + if ($this->metadata->hasValue('EntityAttributes')) { + $metadata['EntityAttributes'] = $this->metadata->getArray('EntityAttributes'); + } + + // add UIInfo extension + if ($this->metadata->hasValue('UIInfo')) { + $metadata['UIInfo'] = $this->metadata->getArray('UIInfo'); + } + + // add RegistrationInfo extension + if ($this->metadata->hasValue('RegistrationInfo')) { + $metadata['RegistrationInfo'] = $this->metadata->getArray('RegistrationInfo'); + } + + // add signature options + if ($this->metadata->hasValue('WantAssertionsSigned')) { + $metadata['saml20.sign.assertion'] = $this->metadata->getBoolean('WantAssertionsSigned'); + } + if ($this->metadata->hasValue('redirect.sign')) { + $metadata['redirect.validate'] = $this->metadata->getBoolean('redirect.sign'); + } elseif ($this->metadata->hasValue('sign.authnrequest')) { + $metadata['validate.authnrequest'] = $this->metadata->getBoolean('sign.authnrequest'); + } + + return $metadata; + } + + + /** + * Retrieve the metadata of an IdP. + * + * @param string $entityId The entity id of the IdP. + * @return \SimpleSAML\Configuration The metadata of the IdP. + */ + public function getIdPMetadata($entityId) + { + assert(is_string($entityId)); + + if ($this->idp !== null && $this->idp !== $entityId) { + throw new Error\Exception('Cannot retrieve metadata for IdP ' . + var_export($entityId, true) . ' because it isn\'t a valid IdP for this SP.'); + } + + $metadataHandler = MetaDataStorageHandler::getMetadataHandler(); + + // First, look in saml20-idp-remote. + try { + return $metadataHandler->getMetaDataConfig($entityId, 'saml20-idp-remote'); + } catch (\Exception $e) { + // Metadata wasn't found + Logger::debug('getIdpMetadata: ' . $e->getMessage()); + } + + // Not found in saml20-idp-remote, look in shib13-idp-remote + try { + return $metadataHandler->getMetaDataConfig($entityId, 'shib13-idp-remote'); + } catch (\Exception $e) { + // Metadata wasn't found + Logger::debug('getIdpMetadata: ' . $e->getMessage()); + } + + // Not found + throw new Error\Exception('Could not find the metadata of an IdP with entity ID ' . + var_export($entityId, true)); + } + + + /** + * Retrieve the metadata of this SP. + * + * @return \SimpleSAML\Configuration The metadata of this SP. + */ + public function getMetadata() + { + return $this->metadata; + } + + + /** + * Get a list with the protocols supported by this SP. + * + * @return array + */ + public function getSupportedProtocols() + { + return $this->protocols; + } + + + /** + * Get the AssertionConsumerService endpoints for a given local SP. + * + * @return array + * @throws \Exception + */ + private function getACSEndpoints(): array + { + // If a list of endpoints is specified in config, take that at face value + if ($this->metadata->hasValue('AssertionConsumerService')) { + return $this->metadata->getArray('AssertionConsumerService'); + } + + $endpoints = []; + $default = [ + Constants::BINDING_HTTP_POST, + 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post', + Constants::BINDING_HTTP_ARTIFACT, + 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01', + ]; + if ($this->metadata->getString('ProtocolBinding', '') === Constants::BINDING_HOK_SSO) { + $default[] = Constants::BINDING_HOK_SSO; + } + + $bindings = $this->metadata->getArray('acs.Bindings', $default); + $index = 0; + foreach ($bindings as $service) { + switch ($service) { + case Constants::BINDING_HTTP_POST: + $acs = [ + 'Binding' => Constants::BINDING_HTTP_POST, + 'Location' => Module::getModuleURL('saml/sp/saml2-acs.php/' . $this->getAuthId()), + ]; + if (!in_array(Constants::NS_SAMLP, $this->protocols, true)) { + $this->protocols[] = Constants::NS_SAMLP; + } + break; + case 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post': + $acs = [ + 'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post', + 'Location' => Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->getAuthId()), + ]; + if (!in_array('urn:oasis:names:tc:SAML:1.0:profiles:browser-post', $this->protocols, true)) { + $this->protocols[] = 'urn:oasis:names:tc:SAML:1.1:protocol'; + } + break; + case Constants::BINDING_HTTP_ARTIFACT: + $acs = [ + 'Binding' => Constants::BINDING_HTTP_ARTIFACT, + 'Location' => Module::getModuleURL('saml/sp/saml2-acs.php/' . $this->getAuthId()), + ]; + if (!in_array(Constants::NS_SAMLP, $this->protocols, true)) { + $this->protocols[] = Constants::NS_SAMLP; + } + break; + case 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01': + $acs = [ + 'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01', + 'Location' => Module::getModuleURL( + 'saml/sp/saml1-acs.php/' . $this->getAuthId() . '/artifact' + ), + ]; + if (!in_array('urn:oasis:names:tc:SAML:1.1:protocol', $this->protocols, true)) { + $this->protocols[] = 'urn:oasis:names:tc:SAML:1.1:protocol'; + } + break; + case Constants::BINDING_HOK_SSO: + $acs = [ + 'Binding' => Constants::BINDING_HOK_SSO, + 'Location' => Module::getModuleURL('saml/sp/saml2-acs.php/' . $this->getAuthId()), + 'hoksso:ProtocolBinding' => Constants::BINDING_HTTP_REDIRECT, + ]; + if (!in_array(Constants::NS_SAMLP, $this->protocols, true)) { + $this->protocols[] = Constants::NS_SAMLP; + } + break; + default: + $acs = []; + } + $acs['index'] = $index; + $endpoints[] = $acs; + $index++; + } + return $endpoints; + } + + + /** + * Get the SingleLogoutService endpoints available for a given local SP. + * + * @return array + * @throws \SimpleSAML\Error\CriticalConfigurationError + */ + private function getSLOEndpoints(): array + { + $store = Store::getInstance(); + $bindings = $this->metadata->getArray( + 'SingleLogoutServiceBinding', + [ + Constants::BINDING_HTTP_REDIRECT, + Constants::BINDING_SOAP, + ] + ); + $defaultLocation = Module::getModuleURL('saml/sp/saml2-logout.php/' . $this->getAuthId()); + $location = $this->metadata->getString('SingleLogoutServiceLocation', $defaultLocation); + + $endpoints = []; + foreach ($bindings as $binding) { + if ($binding == Constants::BINDING_SOAP && !($store instanceof Store\SQL)) { + // we cannot properly support SOAP logout + continue; + } + $endpoints[] = [ + 'Binding' => $binding, + 'Location' => $location, + ]; + } + return $endpoints; + } + + + /** + * Send a SAML1 SSO request to an IdP. + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param array $state The state array for the current authentication. + * @return void + * @deprecated will be removed in a future version + */ + private function startSSO1(Configuration $idpMetadata, array $state): void + { + $idpEntityId = $idpMetadata->getString('entityid'); + + $state['saml:idp'] = $idpEntityId; + + $ar = new Shib13\AuthnRequest(); + $ar->setIssuer($this->entityId); + + $id = Auth\State::saveState($state, 'saml:sp:sso'); + $ar->setRelayState($id); + + $useArtifact = $idpMetadata->getBoolean('saml1.useartifact', null); + if ($useArtifact === null) { + $useArtifact = $this->metadata->getBoolean('saml1.useartifact', false); + } + + if ($useArtifact) { + $shire = Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId . '/artifact'); + } else { + $shire = Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId); + } + + $url = $ar->createRedirect($idpEntityId, $shire); + + Logger::debug('Starting SAML 1 SSO to ' . var_export($idpEntityId, true) . + ' from ' . var_export($this->entityId, true) . '.'); + Utils\HTTP::redirectTrustedURL($url); + } + + + /** + * Send a SAML2 SSO request to an IdP + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param array $state The state array for the current authentication. + * @return void + */ + private function startSSO2(Configuration $idpMetadata, array $state): void + { + if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] < 0) { + Auth\State::throwException( + $state, + new Module\saml\Error\ProxyCountExceeded(Constants::STATUS_RESPONDER) + ); + } + + $ar = Module\saml\Message::buildAuthnRequest($this->metadata, $idpMetadata); + + // GTIS + $ar->setAssertionConsumerServiceURL(Module::getModuleURL('sildisco/sp/saml2-acs.php/'.$this->authId)); + + if (isset($state['\SimpleSAML\Auth\Source.ReturnURL'])) { + $ar->setRelayState($state['\SimpleSAML\Auth\Source.ReturnURL']); + } + + $accr = null; + if ($idpMetadata->getString('AuthnContextClassRef', false)) { + $accr = Utils\Arrays::arrayize($idpMetadata->getString('AuthnContextClassRef')); + } elseif (isset($state['saml:AuthnContextClassRef'])) { + $accr = Utils\Arrays::arrayize($state['saml:AuthnContextClassRef']); + } + + if ($accr !== null) { + $comp = Constants::COMPARISON_EXACT; + if ($idpMetadata->getString('AuthnContextComparison', false)) { + $comp = $idpMetadata->getString('AuthnContextComparison'); + } elseif ( + isset($state['saml:AuthnContextComparison']) + && in_array($state['saml:AuthnContextComparison'], [ + Constants::COMPARISON_EXACT, + Constants::COMPARISON_MINIMUM, + Constants::COMPARISON_MAXIMUM, + Constants::COMPARISON_BETTER, + ], true) + ) { + $comp = $state['saml:AuthnContextComparison']; + } + $ar->setRequestedAuthnContext(['AuthnContextClassRef' => $accr, 'Comparison' => $comp]); + } + + if (isset($state['saml:Audience'])) { + $ar->setAudiences($state['saml:Audience']); + } + if (isset($state['ForceAuthn'])) { + $ar->setForceAuthn((bool) $state['ForceAuthn']); + } + + if (isset($state['isPassive'])) { + $ar->setIsPassive((bool) $state['isPassive']); + } + + if (isset($state['saml:NameID'])) { + if (!is_array($state['saml:NameID']) && !is_a($state['saml:NameID'], NameID::class)) { + throw new Error\Exception('Invalid value of $state[\'saml:NameID\'].'); + } + + $nameId = $state['saml:NameID']; + if (is_array($nameId)) { + // Must be an array > convert to object + + $nid = new NameID(); + if (!array_key_exists('Value', $nameId)) { + throw new \InvalidArgumentException('Missing "Value" in array, cannot create NameID from it.'); + } + + $nid->setValue($nameId['Value']); + if (array_key_exists('NameQualifier', $nameId) && $nameId['NameQualifier'] !== null) { + $nid->setNameQualifier($nameId['NameQualifier']); + } + if (array_key_exists('SPNameQualifier', $nameId) && $nameId['SPNameQualifier'] !== null) { + $nid->setSPNameQualifier($nameId['SPNameQualifier']); + } + if (array_key_exists('SPProvidedID', $nameId) && $nameId['SPProvidedId'] !== null) { + $nid->setSPProvidedID($nameId['SPProvidedID']); + } + if (array_key_exists('Format', $nameId) && $nameId['Format'] !== null) { + $nid->setFormat($nameId['Format']); + } + } else { + $nid = $nameId; + } + + $ar->setNameId($nid); + } + + if (isset($state['saml:NameIDPolicy'])) { + $policy = null; + if (is_string($state['saml:NameIDPolicy'])) { + $policy = [ + 'Format' => (string) $state['saml:NameIDPolicy'], + 'AllowCreate' => true, + ]; + } elseif (is_array($state['saml:NameIDPolicy'])) { + $policy = $state['saml:NameIDPolicy']; + } elseif ($state['saml:NameIDPolicy'] === null) { + $policy = ['Format' => Constants::NAMEID_TRANSIENT]; + } + if ($policy !== null) { + $ar->setNameIdPolicy($policy); + } + } + + $IDPList = []; + $requesterID = []; + + /* Only check for real info for Scoping element if we are going to send Scoping element */ + if ($this->disable_scoping !== true && $idpMetadata->getBoolean('disable_scoping', false) !== true) { + if (isset($state['saml:IDPList'])) { + $IDPList = $state['saml:IDPList']; + } + + if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) { + $ar->setProxyCount($state['saml:ProxyCount']); + } elseif ($idpMetadata->getInteger('ProxyCount', null) !== null) { + $ar->setProxyCount($idpMetadata->getInteger('ProxyCount', null)); + } elseif ($this->metadata->getInteger('ProxyCount', null) !== null) { + $ar->setProxyCount($this->metadata->getInteger('ProxyCount', null)); + } + + $requesterID = []; + if (isset($state['saml:RequesterID'])) { + $requesterID = $state['saml:RequesterID']; + } + + if (isset($state['core:SP'])) { + $requesterID[] = $state['core:SP']; + } + } else { + Logger::debug('Disabling samlp:Scoping for ' . var_export($idpMetadata->getString('entityid'), true)); + } + + $ar->setIDPList( + array_unique( + array_merge( + $this->metadata->getArray('IDPList', []), + $idpMetadata->getArray('IDPList', []), + (array) $IDPList + ) + ) + ); + + $ar->setRequesterID($requesterID); + + // If the downstream SP has set extensions then use them. + // Otherwise use extensions that might be defined in the local SP (only makes sense in a proxy scenario) + if (isset($state['saml:Extensions']) && count($state['saml:Extensions']) > 0) { + $ar->setExtensions($state['saml:Extensions']); + } else if ($this->metadata->getArray('saml:Extensions', null) !== null) { + $ar->setExtensions($this->metadata->getArray('saml:Extensions')); + } + + $providerName = $this->metadata->getString("ProviderName", null); + if ($providerName !== null) { + $ar->setProviderName($providerName); + } + + + // save IdP entity ID as part of the state + $state['ExpectedIssuer'] = $idpMetadata->getString('entityid'); + + $id = Auth\State::saveState($state, 'saml:sp:sso', true); + $ar->setId($id); + + Logger::debug( + 'Sending SAML 2 AuthnRequest to ' . var_export($idpMetadata->getString('entityid'), true) + ); + + // Select appropriate SSO endpoint + if ($ar->getProtocolBinding() === Constants::BINDING_HOK_SSO) { + /** @var array $dst */ + $dst = $idpMetadata->getDefaultEndpoint( + 'SingleSignOnService', + [ + Constants::BINDING_HOK_SSO + ] + ); + } else { + /** @var array $dst */ + $dst = $idpMetadata->getEndpointPrioritizedByBinding( + 'SingleSignOnService', + [ + Constants::BINDING_HTTP_REDIRECT, + Constants::BINDING_HTTP_POST, + ] + ); + } + $ar->setDestination($dst['Location']); + + $b = Binding::getBinding($dst['Binding']); + + $this->sendSAML2AuthnRequest($state, $b, $ar); + + assert(false); + } + + + /** + * Function to actually send the authentication request. + * + * This function does not return. + * + * @param array &$state The state array. + * @param \SAML2\Binding $binding The binding. + * @param \SAML2\AuthnRequest $ar The authentication request. + * @return void + */ + public function sendSAML2AuthnRequest(array &$state, Binding $binding, AuthnRequest $ar) + { + $binding->send($ar); + assert(false); + } + + + /** + * Send a SSO request to an IdP. + * + * @param string $idp The entity ID of the IdP. + * @param array $state The state array for the current authentication. + * @return void + */ + public function startSSO($idp, array $state) + { + assert(is_string($idp)); + + $idpMetadata = $this->getIdPMetadata($idp); + + $type = $idpMetadata->getString('metadata-set'); + switch ($type) { + case 'shib13-idp-remote': + $this->startSSO1($idpMetadata, $state); + assert(false); // Should not return + case 'saml20-idp-remote': + $this->startSSO2($idpMetadata, $state); + assert(false); // Should not return + default: + // Should only be one of the known types + assert(false); + } + } + + + /** + * Start an IdP discovery service operation. + * + * @param array $state The state array. + * @return void + */ + private function startDisco(array $state): void + { + $id = Auth\State::saveState($state, 'saml:sp:sso'); + + $discoURL = $this->discoURL; + if ($discoURL === null) { + // Fallback to internal discovery service + $discoURL = Module::getModuleURL('saml/disco.php'); + } + + $returnTo = Module::getModuleURL('sildisco/sp/discoresp.php', ['AuthID' => $id]); // GTIS + + $params = [ + 'entityID' => $this->entityId, + 'return' => $returnTo, + 'returnIDParam' => 'idpentityid' + ]; + + if (isset($state['saml:IDPList'])) { + $params['IDPList'] = $state['saml:IDPList']; + } + + if (isset($state['isPassive']) && $state['isPassive']) { + $params['isPassive'] = 'true'; + } + + Utils\HTTP::redirectTrustedURL($discoURL, $params); + } + + + /** + * Start login. + * + * This function saves the information about the login, and redirects to the IdP. + * + * @param array &$state Information about the current authentication. + * @return void + */ + public function authenticate(&$state) + { + assert(is_array($state)); + + // We are going to need the authId in order to retrieve this authentication source later + $state['saml:sp:AuthId'] = $this->authId; + + $idp = $this->idp; + + if (isset($state['saml:idp'])) { + $idp = (string) $state['saml:idp']; + } + + if (isset($state['saml:IDPList']) && sizeof($state['saml:IDPList']) > 0) { + // we have a SAML IDPList (we are a proxy): filter the list of IdPs available + $mdh = MetaDataStorageHandler::getMetadataHandler(); + $matchedEntities = $mdh->getMetaDataForEntities($state['saml:IDPList'], 'saml20-idp-remote'); + + if (empty($matchedEntities)) { + // all requested IdPs are unknown + throw new Module\saml\Error\NoSupportedIDP( + Constants::STATUS_REQUESTER, + 'None of the IdPs requested are supported by this proxy.' + ); + } + + if (!is_null($idp) && !array_key_exists($idp, $matchedEntities)) { + // the IdP is enforced but not in the IDPList + throw new Module\saml\Error\NoAvailableIDP( + Constants::STATUS_REQUESTER, + 'None of the IdPs requested are available to this proxy.' + ); + } + + if (is_null($idp) && sizeof($matchedEntities) === 1) { + // only one IdP requested or valid + $idp = key($matchedEntities); + } + } + + if ($idp === null) { + $this->startDisco($state); + assert(false); + } + + $this->startSSO($idp, $state); + assert(false); + } + + + /** + * Re-authenticate an user. + * + * This function is called by the IdP to give the authentication source a chance to + * interact with the user even in the case when the user is already authenticated. + * + * @param array &$state Information about the current authentication. + * @return void + */ + public function reauthenticate(array &$state) + { + $session = Session::getSessionFromRequest(); + $data = $session->getAuthState($this->authId); + $data = $session->getAuthState($this->authId); + if ($data === null) { + throw new Error\NoState(); + } + + foreach ($data as $k => $v) { + $state[$k] = $v; + } + + /* + * GTIS + * If this SP is allowed to use more than one IdP, then send to discovery page + */ + $metadataPath = __DIR__ . '/../../../../../metadata'; + + $spEntityId = $state['SPMetadata']['entityid']; + $IDPList = array_keys(DiscoUtils::getIdpsForSp($spEntityId, $metadataPath)); + + if (sizeof($IDPList) > 1) { + $state['LoginCompletedHandler'] = array(SP::class, 'reauthPostLogin'); + $this->authenticate($state); + assert(false); + } + + // GTIS Changed this if block to avoid logging out before authenticating + // with a new IdP + if (sizeof($IDPList) > 0 && + !in_array($state['saml:sp:IdP'], $IDPList, true)) { + /* + * The user has an existing, valid session. However, the list of IdPs + * accessible to this SP does not include the IdP from the existing + * session. + */ + + Logger::warning( + "Reauthentication is needed. The IdP '${state['saml:sp:IdP']}' is not in the IDPList ". + "accessible to this Service Provider '${state['core:SP']}'." + ); + + $state['LoginCompletedHandler'] = array(SP::class, 'reauthPostLogin'); + $this->authenticate($state); + } + // End GTIS + } + + + /** + * Ask the user to log out before being able to log in again with a + * different identity provider. Note that this method is intended for + * instances of SimpleSAMLphp running as a SAML proxy, and therefore + * acting both as an SP and an IdP at the same time. + * + * This method will never return. + * + * @param array $state The state array. + * The following keys must be defined in the array: + * - 'saml:sp:IdPMetadata': a \SimpleSAML\Configuration object containing + * the metadata of the IdP that authenticated the user in the current + * session. + * - 'saml:sp:AuthId': the identifier of the current authentication source. + * - 'core:IdP': the identifier of the local IdP. + * - 'SPMetadata': an array with the metadata of this local SP. + * + * @return void + * @throws \SimpleSAML\Error\NoPassive In case the authentication request was passive. + */ + public static function askForIdPChange(array &$state) + { + assert(array_key_exists('saml:sp:IdPMetadata', $state)); + assert(array_key_exists('saml:sp:AuthId', $state)); + assert(array_key_exists('core:IdP', $state)); + assert(array_key_exists('SPMetadata', $state)); + + if (isset($state['isPassive']) && (bool) $state['isPassive']) { + // passive request, we cannot authenticate the user + throw new Module\saml\Error\NoPassive( + Constants::STATUS_REQUESTER, + 'Reauthentication required' + ); + } + + // save the state WITHOUT a restart URL, so that we don't try an IdP-initiated login if something goes wrong + $id = Auth\State::saveState($state, 'saml:proxy:invalid_idp', true); + $url = Module::getModuleURL('saml/proxy/invalid_session.php'); + Utils\HTTP::redirectTrustedURL($url, ['AuthState' => $id]); + assert(false); + } + + + /** + * Log the user out before logging in again. + * + * This method will never return. + * + * @param array $state The state array. + * @return void + */ + public static function reauthLogout(array $state) + { + Logger::debug('Proxy: logging the user out before re-authentication.'); + + if (isset($state['Responder'])) { + $state['saml:proxy:reauthLogout:PrevResponder'] = $state['Responder']; + } + $state['Responder'] = [SP::class, 'reauthPostLogout']; + + $idp = IdP::getByState($state); + $idp->handleLogoutRequest($state, null); + assert(false); + } + + + /** + * Complete login operation after re-authenticating the user on another IdP. + * + * @param array $state The authentication state. + * @return void + */ + public static function reauthPostLogin(array $state) + { + assert(isset($state['ReturnCallback'])); + + // Update session state + $session = Session::getSessionFromRequest(); + $authId = $state['saml:sp:AuthId']; + $session->doLogin($authId, Auth\State::getPersistentAuthData($state)); + + // resume the login process + call_user_func($state['ReturnCallback'], $state); + assert(false); + } + + + /** + * Post-logout handler for re-authentication. + * + * This method will never return. + * + * @param \SimpleSAML\IdP $idp The IdP we are logging out from. + * @param array &$state The state array with the state during logout. + * @return void + */ + public static function reauthPostLogout(IdP $idp, array $state) + { + assert(isset($state['saml:sp:AuthId'])); + + Logger::debug('Proxy: logout completed.'); + + if (isset($state['saml:proxy:reauthLogout:PrevResponder'])) { + $state['Responder'] = $state['saml:proxy:reauthLogout:PrevResponder']; + } + + /** @var \SimpleSAML\Module\saml\Auth\Source\SP $sp */ + $sp = Auth\Source::getById($state['saml:sp:AuthId'], Module\saml\Auth\Source\SP::class); + + Logger::debug('Proxy: logging in again.'); + $sp->authenticate($state); + assert(false); + } + + + /** + * Start a SAML 2 logout operation. + * + * @param array $state The logout state. + * @return void + */ + public function startSLO2(&$state) + { + assert(is_array($state)); + assert(array_key_exists('saml:logout:IdP', $state)); + assert(array_key_exists('saml:logout:NameID', $state)); + assert(array_key_exists('saml:logout:SessionIndex', $state)); + + $id = Auth\State::saveState($state, 'saml:slosent'); + + $idp = $state['saml:logout:IdP']; + $nameId = $state['saml:logout:NameID']; + $sessionIndex = $state['saml:logout:SessionIndex']; + + $idpMetadata = $this->getIdPMetadata($idp); + + /** @var array $endpoint */ + $endpoint = $idpMetadata->getEndpointPrioritizedByBinding( + 'SingleLogoutService', + [ + Constants::BINDING_HTTP_REDIRECT, + Constants::BINDING_HTTP_POST + ], + false + ); + if ($endpoint === false) { + Logger::info('No logout endpoint for IdP ' . var_export($idp, true) . '.'); + return; + } + + $lr = Module\saml\Message::buildLogoutRequest($this->metadata, $idpMetadata); + $lr->setNameId($nameId); + $lr->setSessionIndex($sessionIndex); + $lr->setRelayState($id); + $lr->setDestination($endpoint['Location']); + + $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', null); + if ($encryptNameId === null) { + $encryptNameId = $this->metadata->getBoolean('nameid.encryption', false); + } + if ($encryptNameId) { + $lr->encryptNameId(Module\saml\Message::getEncryptionKey($idpMetadata)); + } + + $b = Binding::getBinding($endpoint['Binding']); + $b->send($lr); + + assert(false); + } + + + /** + * Start logout operation. + * + * @param array $state The logout state. + * @return void + */ + public function logout(&$state) + { + assert(is_array($state)); + assert(array_key_exists('saml:logout:Type', $state)); + + $logoutType = $state['saml:logout:Type']; + switch ($logoutType) { + case 'saml1': + // Nothing to do + return; + case 'saml2': + $this->startSLO2($state); + return; + default: + // Should never happen + assert(false); + } + } + + + /** + * Handle a response from a SSO operation. + * + * @param array $state The authentication state. + * @param string $idp The entity id of the IdP. + * @param array $attributes The attributes. + * @return void + */ + public function handleResponse(array $state, $idp, array $attributes) + { + assert(is_string($idp)); + assert(array_key_exists('LogoutState', $state)); + assert(array_key_exists('saml:logout:Type', $state['LogoutState'])); + + $idpMetadata = $this->getIdPMetadata($idp); + + $spMetadataArray = $this->metadata->toArray(); + $idpMetadataArray = $idpMetadata->toArray(); + + /* Save the IdP in the state array. */ + $state['saml:sp:IdP'] = $idp; + $state['PersistentAuthData'][] = 'saml:sp:IdP'; + + $authProcState = [ + 'saml:sp:IdP' => $idp, + 'saml:sp:State' => $state, + 'ReturnCall' => [SP::class, 'onProcessingCompleted'], + + 'Attributes' => $attributes, + 'Destination' => $spMetadataArray, + 'Source' => $idpMetadataArray, + ]; + + if (isset($state['saml:sp:NameID'])) { + $authProcState['saml:sp:NameID'] = $state['saml:sp:NameID']; + } + if (isset($state['saml:sp:SessionIndex'])) { + $authProcState['saml:sp:SessionIndex'] = $state['saml:sp:SessionIndex']; + } + + $pc = new Auth\ProcessingChain($idpMetadataArray, $spMetadataArray, 'sp'); + $pc->processState($authProcState); + + self::onProcessingCompleted($authProcState); + } + + + /** + * Handle a logout request from an IdP. + * + * @param string $idpEntityId The entity ID of the IdP. + * @return void + */ + public function handleLogout($idpEntityId) + { + assert(is_string($idpEntityId)); + + /* Call the logout callback we registered in onProcessingCompleted(). */ + $this->callLogoutCallback($idpEntityId); + } + + + /** + * Handle an unsolicited login operations. + * + * This method creates a session from the information received. It will + * then redirect to the given URL. This is used to handle IdP initiated + * SSO. This method will never return. + * + * @param string $authId The id of the authentication source that received the request. + * @param array $state A state array. + * @param string $redirectTo The URL we should redirect the user to after updating + * the session. The function will check if the URL is allowed, so there is no need to + * manually check the URL on beforehand. Please refer to the 'trusted.url.domains' + * configuration directive for more information about allowing (or disallowing) URLs. + * @return void + */ + public static function handleUnsolicitedAuth($authId, array $state, $redirectTo) + { + assert(is_string($authId)); + assert(is_string($redirectTo)); + + $session = Session::getSessionFromRequest(); + $session->doLogin($authId, Auth\State::getPersistentAuthData($state)); + + Utils\HTTP::redirectUntrustedURL($redirectTo); + } + + + /** + * Called when we have completed the procssing chain. + * + * @param array $authProcState The processing chain state. + * @return void + */ + public static function onProcessingCompleted(array $authProcState) + { + assert(array_key_exists('saml:sp:IdP', $authProcState)); + assert(array_key_exists('saml:sp:State', $authProcState)); + assert(array_key_exists('Attributes', $authProcState)); + + $idp = $authProcState['saml:sp:IdP']; + $state = $authProcState['saml:sp:State']; + + $sourceId = $state['saml:sp:AuthId']; + + /** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */ + $source = Auth\Source::getById($sourceId); + if ($source === null) { + throw new \Exception('Could not find authentication source with id ' . $sourceId); + } + + // Register a callback that we can call if we receive a logout request from the IdP + $source->addLogoutCallback($idp, $state); + + $state['Attributes'] = $authProcState['Attributes']; + + if (isset($state['saml:sp:isUnsolicited']) && (bool) $state['saml:sp:isUnsolicited']) { + if (!empty($state['saml:sp:RelayState'])) { + $redirectTo = $state['saml:sp:RelayState']; + } else { + $redirectTo = $source->getMetadata()->getString('RelayState', '/'); + } + self::handleUnsolicitedAuth($sourceId, $state, $redirectTo); + } + + Auth\Source::completeAuth($state); + } +} diff --git a/modules/sildisco/lib/IdP/SAML2.php b/modules/sildisco/lib/IdP/SAML2.php new file mode 100644 index 00000000..4d975bd6 --- /dev/null +++ b/modules/sildisco/lib/IdP/SAML2.php @@ -0,0 +1,1515 @@ +getConfig(); + + $assertion = self::buildAssertion($idpMetadata, $spMetadata, $state); + + if (isset($state['saml:AuthenticatingAuthority'])) { + $assertion->setAuthenticatingAuthority($state['saml:AuthenticatingAuthority']); + } + + // create the session association (for logout) + $association = [ + 'id' => 'saml:' . $spEntityId, + 'Handler' => '\SimpleSAML\Module\saml\IdP\SAML2', + 'Expires' => $assertion->getSessionNotOnOrAfter(), + 'saml:entityID' => $spEntityId, + 'saml:NameID' => $state['saml:idp:NameID'], + 'saml:SessionIndex' => $assertion->getSessionIndex(), + ]; + + // maybe encrypt the assertion + $assertion = self::encryptAssertion($idpMetadata, $spMetadata, $assertion); + + // create the response + $ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL); + $ar->setInResponseTo($requestId); + $ar->setRelayState($relayState); + $ar->setAssertions([$assertion]); + + // register the session association with the IdP + $idp->addAssociation($association); + + $statsData = [ + 'spEntityID' => $spEntityId, + 'idpEntityID' => $idpMetadata->getString('entityid'), + 'protocol' => 'saml2', + ]; + if (isset($state['saml:AuthnRequestReceivedAt'])) { + $statsData['logintime'] = microtime(true) - $state['saml:AuthnRequestReceivedAt']; + } + Stats::log('saml:idp:Response', $statsData); + + // send the response + $binding = Binding::getBinding($protocolBinding); + $binding->send($ar); + } + + + /** + * Handle authentication error. + * + * \SimpleSAML\Error\Exception $exception The exception. + * + * @param array $state The error state. + * @return void + */ + public static function handleAuthError(Error\Exception $exception, array $state) + { + assert(isset($state['SPMetadata'])); + assert(isset($state['saml:ConsumerURL'])); + assert(array_key_exists('saml:RequestId', $state)); // Can be NULL. + assert(array_key_exists('saml:RelayState', $state)); // Can be NULL. + + $spMetadata = $state["SPMetadata"]; + $spEntityId = $spMetadata['entityid']; + $spMetadata = Configuration::loadFromArray( + $spMetadata, + '$metadata[' . var_export($spEntityId, true) . ']' + ); + + $requestId = $state['saml:RequestId']; + $relayState = $state['saml:RelayState']; + $consumerURL = $state['saml:ConsumerURL']; + $protocolBinding = $state['saml:Binding']; + + $idp = IdP::getByState($state); + + $idpMetadata = $idp->getConfig(); + + $error = \SimpleSAML\Module\saml\Error::fromException($exception); + + Logger::warning("Returning error to SP with entity ID '" . var_export($spEntityId, true) . "'."); + $exception->log(Logger::WARNING); + + $ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL); + $ar->setInResponseTo($requestId); + $ar->setRelayState($relayState); + + $status = [ + 'Code' => $error->getStatus(), + 'SubCode' => $error->getSubStatus(), + 'Message' => $error->getStatusMessage(), + ]; + $ar->setStatus($status); + + $statsData = [ + 'spEntityID' => $spEntityId, + 'idpEntityID' => $idpMetadata->getString('entityid'), + 'protocol' => 'saml2', + 'error' => $status, + ]; + if (isset($state['saml:AuthnRequestReceivedAt'])) { + $statsData['logintime'] = microtime(true) - $state['saml:AuthnRequestReceivedAt']; + } + Stats::log('saml:idp:Response:error', $statsData); + + $binding = Binding::getBinding($protocolBinding); + $binding->send($ar); + } + + + /** + * Find SP AssertionConsumerService based on parameter in AuthnRequest. + * + * @param array $supportedBindings The bindings we allow for the response. + * @param \SimpleSAML\Configuration $spMetadata The metadata for the SP. + * @param string|null $AssertionConsumerServiceURL AssertionConsumerServiceURL from request. + * @param string|null $ProtocolBinding ProtocolBinding from request. + * @param int|null $AssertionConsumerServiceIndex AssertionConsumerServiceIndex from request. + * + * @return array|null Array with the Location and Binding we should use for the response. + */ + private static function getAssertionConsumerService( + array $supportedBindings, + Configuration $spMetadata, + string $AssertionConsumerServiceURL = null, + string $ProtocolBinding = null, + int $AssertionConsumerServiceIndex = null + ): ?array { + /* We want to pick the best matching endpoint in the case where for example + * only the ProtocolBinding is given. We therefore pick endpoints with the + * following priority: + * 1. isDefault="true" + * 2. isDefault unset + * 3. isDefault="false" + */ + $firstNotFalse = null; + $firstFalse = null; + foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) { + if ($AssertionConsumerServiceURL !== null && $ep['Location'] !== $AssertionConsumerServiceURL) { + continue; + } + if ($ProtocolBinding !== null && $ep['Binding'] !== $ProtocolBinding) { + continue; + } + if ($AssertionConsumerServiceIndex !== null && $ep['index'] !== $AssertionConsumerServiceIndex) { + continue; + } + + if (!in_array($ep['Binding'], $supportedBindings, true)) { + /* The endpoint has an unsupported binding. */ + continue; + } + + // we have an endpoint that matches all our requirements. Check if it is the best one + + if (array_key_exists('isDefault', $ep)) { + if ($ep['isDefault'] === true) { + // this is the first matching endpoint with isDefault set to true + return $ep; + } + // isDefault is set to FALSE, but the endpoint is still usable + if ($firstFalse === null) { + // this is the first endpoint that we can use + $firstFalse = $ep; + } + } else { + if ($firstNotFalse === null) { + // this is the first endpoint without isDefault set + $firstNotFalse = $ep; + } + } + } + + if ($firstNotFalse !== null) { + return $firstNotFalse; + } elseif ($firstFalse !== null) { + return $firstFalse; + } + + Logger::warning('Authentication request specifies invalid AssertionConsumerService:'); + if ($AssertionConsumerServiceURL !== null) { + Logger::warning('AssertionConsumerServiceURL: ' . var_export($AssertionConsumerServiceURL, true)); + } + if ($ProtocolBinding !== null) { + Logger::warning('ProtocolBinding: ' . var_export($ProtocolBinding, true)); + } + if ($AssertionConsumerServiceIndex !== null) { + Logger::warning( + 'AssertionConsumerServiceIndex: ' . var_export($AssertionConsumerServiceIndex, true) + ); + } + + // we have no good endpoints. Our last resort is to just use the default endpoint + return $spMetadata->getDefaultEndpoint('AssertionConsumerService', $supportedBindings); + } + + + /** + * Receive an authentication request. + * + * @param \SimpleSAML\IdP $idp The IdP we are receiving it for. + * @return void + * @throws \SimpleSAML\Error\BadRequest In case an error occurs when trying to receive the request. + */ + public static function receiveAuthnRequest(\SimpleSAML\IdP $idp) + { + $metadata = MetaDataStorageHandler::getMetadataHandler(); + $idpMetadata = $idp->getConfig(); + + $supportedBindings = [Constants::BINDING_HTTP_POST]; + if ($idpMetadata->getBoolean('saml20.sendartifact', false)) { + $supportedBindings[] = Constants::BINDING_HTTP_ARTIFACT; + } + if ($idpMetadata->getBoolean('saml20.hok.assertion', false)) { + $supportedBindings[] = Constants::BINDING_HOK_SSO; + } + if ($idpMetadata->getBoolean('saml20.ecp', false)) { + $supportedBindings[] = Constants::BINDING_PAOS; + } + + if (isset($_REQUEST['spentityid']) || isset($_REQUEST['providerId'])) { + /* IdP initiated authentication. */ + + if (isset($_REQUEST['cookieTime'])) { + $cookieTime = (int) $_REQUEST['cookieTime']; + if ($cookieTime + 5 > time()) { + /* + * Less than five seconds has passed since we were + * here the last time. Cookies are probably disabled. + */ + Utils\HTTP::checkSessionCookie(Utils\HTTP::getSelfURL()); + } + } + + $spEntityId = (string) isset($_REQUEST['spentityid']) ? $_REQUEST['spentityid'] : $_REQUEST['providerId']; + $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); + + if (isset($_REQUEST['RelayState'])) { + $relayState = (string) $_REQUEST['RelayState']; + } elseif (isset($_REQUEST['target'])) { + $relayState = (string) $_REQUEST['target']; + } else { + $relayState = null; + } + + if (isset($_REQUEST['binding'])) { + $protocolBinding = (string) $_REQUEST['binding']; + } else { + $protocolBinding = null; + } + + if (isset($_REQUEST['NameIDFormat'])) { + $nameIDFormat = (string) $_REQUEST['NameIDFormat']; + } else { + $nameIDFormat = null; + } + + if (isset($_REQUEST['ConsumerURL'])) { + $consumerURL = (string)$_REQUEST['ConsumerURL']; + } elseif (isset($_REQUEST['shire'])) { + $consumerURL = (string)$_REQUEST['shire']; + } else { + $consumerURL = null; + } + + $requestId = null; + $IDPList = []; + $ProxyCount = null; + $RequesterID = null; + $forceAuthn = false; + $isPassive = false; + $consumerIndex = null; + $extensions = null; + $allowCreate = true; + $authnContext = null; + + $idpInit = true; + + Logger::info( + 'SAML2.0 - IdP.SSOService: IdP initiated authentication: ' . var_export($spEntityId, true) + ); + } else { + try { + $binding = Binding::getCurrentBinding(); + } catch (Exception $e) { + header($_SERVER["SERVER_PROTOCOL"]." 405 Method Not Allowed", true, 405); + exit; + } + $request = $binding->receive(); + + if (!($request instanceof AuthnRequest)) { + throw new Error\BadRequest( + 'Message received on authentication request endpoint wasn\'t an authentication request.' + ); + } + + /** @psalm-var null|string|\SAML2\XML\saml\Issuer $issuer Remove in SSP 2.0 */ + $issuer = $request->getIssuer(); + if ($issuer === null) { + throw new Error\BadRequest( + 'Received message on authentication request endpoint without issuer.' + ); + } elseif ($issuer instanceof Issuer) { + /** @psalm-var string|null $spEntityId */ + $spEntityId = $issuer->getValue(); + if ($spEntityId === null) { + /* Without an issuer we have no way to respond to the message. */ + throw new Error\BadRequest('Received message on logout endpoint without issuer.'); + } + } else { // we got a string, old case + $spEntityId = $issuer; + } + $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); + + \SimpleSAML\Module\saml\Message::validateMessage($spMetadata, $idpMetadata, $request); + + $relayState = $request->getRelayState(); + + $requestId = $request->getId(); + $IDPList = $request->getIDPList(); + $ProxyCount = $request->getProxyCount(); + if ($ProxyCount !== null) { + $ProxyCount--; + } + $RequesterID = $request->getRequesterID(); + $forceAuthn = $request->getForceAuthn(); + $isPassive = $request->getIsPassive(); + $consumerURL = $request->getAssertionConsumerServiceURL(); + $protocolBinding = $request->getProtocolBinding(); + $consumerIndex = $request->getAssertionConsumerServiceIndex(); + $extensions = $request->getExtensions(); + $authnContext = $request->getRequestedAuthnContext(); + + $nameIdPolicy = $request->getNameIdPolicy(); + if (isset($nameIdPolicy['Format'])) { + $nameIDFormat = $nameIdPolicy['Format']; + } else { + $nameIDFormat = null; + } + if (isset($nameIdPolicy['AllowCreate'])) { + $allowCreate = $nameIdPolicy['AllowCreate']; + } else { + $allowCreate = false; + } + + $idpInit = false; + + Logger::info( + 'SAML2.0 - IdP.SSOService: incoming authentication request: ' . var_export($spEntityId, true) + ); + } + + Stats::log('saml:idp:AuthnRequest', [ + 'spEntityID' => $spEntityId, + 'idpEntityID' => $idpMetadata->getString('entityid'), + 'forceAuthn' => $forceAuthn, + 'isPassive' => $isPassive, + 'protocol' => 'saml2', + 'idpInit' => $idpInit, + ]); + + $acsEndpoint = self::getAssertionConsumerService( + $supportedBindings, + $spMetadata, + $consumerURL, + $protocolBinding, + $consumerIndex + ); + if ($acsEndpoint === null) { + throw new Exception('Unable to use any of the ACS endpoints found for SP \'' . $spEntityId . '\''); + } + + $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', []))); + if ($ProxyCount === null) { + $ProxyCount = $spMetadata->getInteger('ProxyCount', null); + } + + if (!$forceAuthn) { + $forceAuthn = $spMetadata->getBoolean('ForceAuthn', false); + } + + $sessionLostParams = [ + 'spentityid' => $spEntityId, + ]; + if ($relayState !== null) { + $sessionLostParams['RelayState'] = $relayState; + } + /* + Putting cookieTime as the last parameter makes unit testing easier since we don't need to handle a + changing time component in the middle of the url + */ + $sessionLostParams['cookieTime'] = time(); + + $sessionLostURL = Utils\HTTP::addURLParameters( + Utils\HTTP::getSelfURLNoQuery(), + $sessionLostParams + ); + + + /* + * Added by GTIS. + * This code is intended to ensure that a session from a new SP + * will be forced to reauthenticate if that SP is not allowed + * to authenticate through any of the IDP's that have so far + * been used for authentication. + * + * In order for this for this to avoid forcing authentication + * in every case, the hub's saml20-idp-hosted.php entry needs + * to include an authproc entry that adds each authenticating + * IDP to a list in the session. + * That list should be found in ... + * sessionDataType: 'sildisco:authentication' + * sessionKey: 'authenticated_idps' + * + * Another feature is that it forces the user to the discovery page, + * if the SP is allowed to use more than one IDP. The reason for this + * is that we want the user to be able to pick which of his ID's + * to use for this session. The way it is carried out is by expiring + * the user's session on the hub. (It does not log the user out from + * any of the IDP's.) + * + */ + $session = \SimpleSAML\Session::getSessionFromRequest(); + $sessionDataType = 'sildisco:authentication'; + $spIdKey = 'spentityid'; + $session->setData($sessionDataType, $spIdKey, $spEntityId); + $metadataPath = __DIR__ . '/../../../../metadata'; + $IDPList = array_keys(DiscoUtils::getIdpsForSp($spEntityId, $metadataPath)); + if ( ! $forceAuthn ) { + $sessionDataType = 'sildisco:authentication'; + $sessionKey = 'authenticated_idps'; + $authenticatedIdps = $session->getData($sessionDataType, $sessionKey); + + if ($authenticatedIdps) { + $metadataPath = __DIR__ . '/../../../../metadata/'; + $allowedIdps = DiscoUtils::getReducedIdpList( + $authenticatedIdps, + $metadataPath, + $spEntityId + ); + if ( ! $allowedIdps) { + $IDPList = Null; + $forceAuthn = True; + } + } else { // If there are no authenticated IDPs + $forceAuthn = True; + } + } + /* + * End of GTIS addition + */ + + $state = [ + 'Responder' => ['\SimpleSAML\Module\saml\IdP\SAML2', 'sendResponse'], + Auth\State::EXCEPTION_HANDLER_FUNC => [ + '\SimpleSAML\Module\saml\IdP\SAML2', + 'handleAuthError' + ], + Auth\State::RESTART => $sessionLostURL, + + 'SPMetadata' => $spMetadata->toArray(), + 'saml:RelayState' => $relayState, + 'saml:RequestId' => $requestId, + 'saml:IDPList' => $IDPList, + 'saml:ProxyCount' => $ProxyCount, + 'saml:RequesterID' => $RequesterID, + 'ForceAuthn' => $forceAuthn, + 'isPassive' => $isPassive, + 'saml:ConsumerURL' => $acsEndpoint['Location'], + 'saml:Binding' => $acsEndpoint['Binding'], + 'saml:NameIDFormat' => $nameIDFormat, + 'saml:AllowCreate' => $allowCreate, + 'saml:Extensions' => $extensions, + 'saml:AuthnRequestReceivedAt' => microtime(true), + 'saml:RequestedAuthnContext' => $authnContext, + ]; + + $idp->handleAuthenticationRequest($state); + } + + + /** + * Send a logout request to a given association. + * + * @param \SimpleSAML\IdP $idp The IdP we are sending a logout request from. + * @param array $association The association that should be terminated. + * @param string|null $relayState An id that should be carried across the logout. + * @return void + */ + public static function sendLogoutRequest(IdP $idp, array $association, $relayState) + { + assert(is_string($relayState) || $relayState === null); + + Logger::info('Sending SAML 2.0 LogoutRequest to: ' . var_export($association['saml:entityID'], true)); + + $metadata = MetaDataStorageHandler::getMetadataHandler(); + $idpMetadata = $idp->getConfig(); + $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); + + Stats::log('saml:idp:LogoutRequest:sent', [ + 'spEntityID' => $association['saml:entityID'], + 'idpEntityID' => $idpMetadata->getString('entityid'), + ]); + + /** @var array $dst */ + $dst = $spMetadata->getEndpointPrioritizedByBinding( + 'SingleLogoutService', + [ + Constants::BINDING_HTTP_REDIRECT, + Constants::BINDING_HTTP_POST + ] + ); + $binding = Binding::getBinding($dst['Binding']); + $lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState); + $lr->setDestination($dst['Location']); + + $binding->send($lr); + } + + + /** + * Send a logout response. + * + * @param \SimpleSAML\IdP $idp The IdP we are sending a logout request from. + * @param array &$state The logout state array. + * @return void + */ + public static function sendLogoutResponse(IdP $idp, array $state) + { + assert(isset($state['saml:SPEntityId'])); + assert(isset($state['saml:RequestId'])); + assert(array_key_exists('saml:RelayState', $state)); // Can be NULL. + + $spEntityId = $state['saml:SPEntityId']; + + $metadata = MetaDataStorageHandler::getMetadataHandler(); + $idpMetadata = $idp->getConfig(); + $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); + + $lr = \SimpleSAML\Module\saml\Message::buildLogoutResponse($idpMetadata, $spMetadata); + $lr->setInResponseTo($state['saml:RequestId']); + $lr->setRelayState($state['saml:RelayState']); + + if (isset($state['core:Failed']) && $state['core:Failed']) { + $partial = true; + $lr->setStatus([ + 'Code' => Constants::STATUS_SUCCESS, + 'SubCode' => Constants::STATUS_PARTIAL_LOGOUT, + ]); + Logger::info('Sending logout response for partial logout to SP ' . var_export($spEntityId, true)); + } else { + $partial = false; + Logger::debug('Sending logout response to SP ' . var_export($spEntityId, true)); + } + + Stats::log('saml:idp:LogoutResponse:sent', [ + 'spEntityID' => $spEntityId, + 'idpEntityID' => $idpMetadata->getString('entityid'), + 'partial' => $partial + ]); + + /** @var array $dst */ + $dst = $spMetadata->getEndpointPrioritizedByBinding( + 'SingleLogoutService', + [ + Constants::BINDING_HTTP_REDIRECT, + Constants::BINDING_HTTP_POST + ] + ); + $binding = Binding::getBinding($dst['Binding']); + if (isset($dst['ResponseLocation'])) { + $dst = $dst['ResponseLocation']; + } else { + $dst = $dst['Location']; + } + $lr->setDestination($dst); + + $binding->send($lr); + } + + + /** + * Receive a logout message. + * + * @param \SimpleSAML\IdP $idp The IdP we are receiving it for. + * @return void + * @throws \SimpleSAML\Error\BadRequest In case an error occurs while trying to receive the logout message. + */ + public static function receiveLogoutMessage(IdP $idp) + { + $binding = Binding::getCurrentBinding(); + $message = $binding->receive(); + + /** @psalm-var null|string|\SAML2\XML\saml\Issuer Remove in SSP 2.0 */ + $issuer = $message->getIssuer(); + if ($issuer === null) { + /* Without an issuer we have no way to respond to the message. */ + throw new Error\BadRequest('Received message on logout endpoint without issuer.'); + } elseif ($issuer instanceof Issuer) { + /** @psalm-var string|null $spEntityId */ + $spEntityId = $issuer->getValue(); + if ($spEntityId === null) { + /* Without an issuer we have no way to respond to the message. */ + throw new Error\BadRequest('Received message on logout endpoint without issuer.'); + } + } else { + $spEntityId = $issuer; + } + + $metadata = MetaDataStorageHandler::getMetadataHandler(); + $idpMetadata = $idp->getConfig(); + $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); + + \SimpleSAML\Module\saml\Message::validateMessage($spMetadata, $idpMetadata, $message); + + if ($message instanceof LogoutResponse) { + Logger::info('Received SAML 2.0 LogoutResponse from: ' . var_export($spEntityId, true)); + $statsData = [ + 'spEntityID' => $spEntityId, + 'idpEntityID' => $idpMetadata->getString('entityid'), + ]; + if (!$message->isSuccess()) { + $statsData['error'] = $message->getStatus(); + } + Stats::log('saml:idp:LogoutResponse:recv', $statsData); + + $relayState = $message->getRelayState(); + + if (!$message->isSuccess()) { + $logoutError = \SimpleSAML\Module\saml\Message::getResponseError($message); + Logger::warning('Unsuccessful logout. Status was: ' . $logoutError); + } else { + $logoutError = null; + } + + $assocId = 'saml:' . $spEntityId; + + $idp->handleLogoutResponse($assocId, $relayState, $logoutError); + } elseif ($message instanceof LogoutRequest) { + Logger::info('Received SAML 2.0 LogoutRequest from: ' . var_export($spEntityId, true)); + Stats::log('saml:idp:LogoutRequest:recv', [ + 'spEntityID' => $spEntityId, + 'idpEntityID' => $idpMetadata->getString('entityid'), + ]); + + $spStatsId = $spMetadata->getString('core:statistics-id', $spEntityId); + Logger::stats('saml20-idp-SLO spinit ' . $spStatsId . ' ' . $idpMetadata->getString('entityid')); + + $state = [ + 'Responder' => ['\SimpleSAML\Module\saml\IdP\SAML2', 'sendLogoutResponse'], + 'saml:SPEntityId' => $spEntityId, + 'saml:RelayState' => $message->getRelayState(), + 'saml:RequestId' => $message->getId(), + ]; + + $assocId = 'saml:' . $spEntityId; + $idp->handleLogoutRequest($state, $assocId); + } else { + throw new Error\BadRequest('Unknown message received on logout endpoint: ' . get_class($message)); + } + } + + + /** + * Retrieve a logout URL for a given logout association. + * + * @param \SimpleSAML\IdP $idp The IdP we are sending a logout request from. + * @param array $association The association that should be terminated. + * @param string|NULL $relayState An id that should be carried across the logout. + * + * @return string The logout URL. + */ + public static function getLogoutURL(IdP $idp, array $association, $relayState) + { + assert(is_string($relayState) || $relayState === null); + + Logger::info('Sending SAML 2.0 LogoutRequest to: ' . var_export($association['saml:entityID'], true)); + + $metadata = MetaDataStorageHandler::getMetadataHandler(); + $idpMetadata = $idp->getConfig(); + $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); + + $bindings = [ + Constants::BINDING_HTTP_REDIRECT, + Constants::BINDING_HTTP_POST + ]; + + /** @var array $dst */ + $dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', $bindings); + + if ($dst['Binding'] === Constants::BINDING_HTTP_POST) { + $params = ['association' => $association['id'], 'idp' => $idp->getId()]; + if ($relayState !== null) { + $params['RelayState'] = $relayState; + } + return Module::getModuleURL('core/idp/logout-iframe-post.php', $params); + } + + $lr = self::buildLogoutRequest($idpMetadata, $spMetadata, $association, $relayState); + $lr->setDestination($dst['Location']); + + $binding = new HTTPRedirect(); + return $binding->getRedirectURL($lr); + } + + + /** + * Retrieve the metadata for the given SP association. + * + * @param \SimpleSAML\IdP $idp The IdP the association belongs to. + * @param array $association The SP association. + * + * @return \SimpleSAML\Configuration Configuration object for the SP metadata. + */ + public static function getAssociationConfig(IdP $idp, array $association) + { + $metadata = MetaDataStorageHandler::getMetadataHandler(); + try { + return $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote'); + } catch (Exception $e) { + return Configuration::loadFromArray([], 'Unknown SAML 2 entity.'); + } + } + + + /** + * Retrieve the metadata of a hosted SAML 2 IdP. + * + * @param string $entityid The entity ID of the hosted SAML 2 IdP whose metadata we want. + * + * @return array + * @throws \SimpleSAML\Error\CriticalConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\MetadataNotFound + */ + public static function getHostedMetadata($entityid) + { + $handler = MetaDataStorageHandler::getMetadataHandler(); + $config = $handler->getMetaDataConfig($entityid, 'saml20-idp-hosted'); + + // configure endpoints + $ssob = $handler->getGenerated('SingleSignOnServiceBinding', 'saml20-idp-hosted'); + $slob = $handler->getGenerated('SingleLogoutServiceBinding', 'saml20-idp-hosted'); + $ssol = $handler->getGenerated('SingleSignOnService', 'saml20-idp-hosted'); + $slol = $handler->getGenerated('SingleLogoutService', 'saml20-idp-hosted'); + + $sso = []; + if (is_array($ssob)) { + foreach ($ssob as $binding) { + $sso[] = [ + 'Binding' => $binding, + 'Location' => $ssol, + ]; + } + } else { + $sso[] = [ + 'Binding' => $ssob, + 'Location' => $ssol, + ]; + } + + $slo = []; + if (is_array($slob)) { + foreach ($slob as $binding) { + $slo[] = [ + 'Binding' => $binding, + 'Location' => $slol, + ]; + } + } else { + $slo[] = [ + 'Binding' => $slob, + 'Location' => $slol, + ]; + } + + $metadata = [ + 'metadata-set' => 'saml20-idp-hosted', + 'entityid' => $entityid, + 'SingleSignOnService' => $sso, + 'SingleLogoutService' => $slo, + 'NameIDFormat' => $config->getArrayizeString('NameIDFormat', Constants::NAMEID_TRANSIENT), + ]; + + // add certificates + $keys = []; + $certInfo = Utils\Crypto::loadPublicKey($config, false, 'new_'); + $hasNewCert = false; + if ($certInfo !== null) { + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => true, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => 'new_', + ]; + $hasNewCert = true; + } + + /** @var array $certInfo */ + $certInfo = Utils\Crypto::loadPublicKey($config, true); + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => $hasNewCert === false, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => '', + ]; + + if ($config->hasValue('https.certificate')) { + /** @var array $httpsCert */ + $httpsCert = Utils\Crypto::loadPublicKey($config, true, 'https.'); + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => false, + 'X509Certificate' => $httpsCert['certData'], + 'prefix' => 'https.' + ]; + } + $metadata['keys'] = $keys; + + // add ArtifactResolutionService endpoint, if enabled + if ($config->getBoolean('saml20.sendartifact', false)) { + $metadata['ArtifactResolutionService'][] = [ + 'index' => 0, + 'Binding' => Constants::BINDING_SOAP, + 'Location' => Utils\HTTP::getBaseURL() . 'saml2/idp/ArtifactResolutionService.php' + ]; + } + + // add Holder of Key, if enabled + if ($config->getBoolean('saml20.hok.assertion', false)) { + array_unshift( + $metadata['SingleSignOnService'], + [ + 'hoksso:ProtocolBinding' => Constants::BINDING_HTTP_REDIRECT, + 'Binding' => Constants::BINDING_HOK_SSO, + 'Location' => Utils\HTTP::getBaseURL() . 'saml2/idp/SSOService.php', + ] + ); + } + + // add ECP profile, if enabled + if ($config->getBoolean('saml20.ecp', false)) { + $metadata['SingleSignOnService'][] = [ + 'index' => 0, + 'Binding' => Constants::BINDING_SOAP, + 'Location' => Utils\HTTP::getBaseURL() . 'saml2/idp/SSOService.php', + ]; + } + + // add organization information + if ($config->hasValue('OrganizationName')) { + $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName'); + $metadata['OrganizationDisplayName'] = $config->getLocalizedString( + 'OrganizationDisplayName', + $metadata['OrganizationName'] + ); + + if (!$config->hasValue('OrganizationURL')) { + throw new Error\Exception('If OrganizationName is set, OrganizationURL must also be set.'); + } + $metadata['OrganizationURL'] = $config->getLocalizedString('OrganizationURL'); + } + + // add scope + if ($config->hasValue('scope')) { + $metadata['scope'] = $config->getArray('scope'); + } + + // add extensions + if ($config->hasValue('EntityAttributes')) { + $metadata['EntityAttributes'] = $config->getArray('EntityAttributes'); + + // check for entity categories + if (Utils\Config\Metadata::isHiddenFromDiscovery($metadata)) { + $metadata['hide.from.discovery'] = true; + } + } + + if ($config->hasValue('UIInfo')) { + $metadata['UIInfo'] = $config->getArray('UIInfo'); + } + + if ($config->hasValue('DiscoHints')) { + $metadata['DiscoHints'] = $config->getArray('DiscoHints'); + } + + if ($config->hasValue('RegistrationInfo')) { + $metadata['RegistrationInfo'] = $config->getArray('RegistrationInfo'); + } + + // configure signature options + if ($config->hasValue('validate.authnrequest')) { + $metadata['sign.authnrequest'] = $config->getBoolean('validate.authnrequest'); + } + + if ($config->hasValue('redirect.validate')) { + $metadata['redirect.sign'] = $config->getBoolean('redirect.validate'); + } + + // add contact information + if ($config->hasValue('contacts')) { + $contacts = $config->getArray('contacts'); + foreach ($contacts as $contact) { + $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); + } + } + + $globalConfig = Configuration::getInstance(); + $email = $globalConfig->getString('technicalcontact_email', false); + if ($email && $email !== 'na@example.org') { + $contact = [ + 'emailAddress' => $email, + 'name' => $globalConfig->getString('technicalcontact_name', null), + 'contactType' => 'technical', + ]; + $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); + } + + return $metadata; + } + + + /** + * Calculate the NameID value that should be used. + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param \SimpleSAML\Configuration $spMetadata The metadata of the SP. + * @param array &$state The authentication state of the user. + * + * @return string|null The NameID value. + */ + private static function generateNameIdValue( + Configuration $idpMetadata, + Configuration $spMetadata, + array &$state + ): ?string { + $attribute = $spMetadata->getString('simplesaml.nameidattribute', null); + if ($attribute === null) { + $attribute = $idpMetadata->getString('simplesaml.nameidattribute', null); + if ($attribute === null) { + if (!isset($state['UserID'])) { + Logger::error('Unable to generate NameID. Check the userid.attribute option.'); + return null; + } + $attributeValue = $state['UserID']; + $idpEntityId = $idpMetadata->getString('entityid'); + $spEntityId = $spMetadata->getString('entityid'); + + $secretSalt = Utils\Config::getSecretSalt(); + + $uidData = 'uidhashbase' . $secretSalt; + $uidData .= strlen($idpEntityId) . ':' . $idpEntityId; + $uidData .= strlen($spEntityId) . ':' . $spEntityId; + $uidData .= strlen($attributeValue) . ':' . $attributeValue; + $uidData .= $secretSalt; + + return hash('sha1', $uidData); + } + } + + $attributes = $state['Attributes']; + if (!array_key_exists($attribute, $attributes)) { + Logger::error('Unable to add NameID: Missing ' . var_export($attribute, true) . + ' in the attributes of the user.'); + return null; + } + + return $attributes[$attribute][0]; + } + + + /** + * Helper function for encoding attributes. + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param \SimpleSAML\Configuration $spMetadata The metadata of the SP. + * @param array $attributes The attributes of the user. + * + * @return array The encoded attributes. + * + * @throws \SimpleSAML\Error\Exception In case an unsupported encoding is specified by configuration. + */ + private static function encodeAttributes( + Configuration $idpMetadata, + Configuration $spMetadata, + array $attributes + ): array { + $base64Attributes = $spMetadata->getBoolean('base64attributes', null); + if ($base64Attributes === null) { + $base64Attributes = $idpMetadata->getBoolean('base64attributes', false); + } + + if ($base64Attributes) { + $defaultEncoding = 'base64'; + } else { + $defaultEncoding = 'string'; + } + + $srcEncodings = $idpMetadata->getArray('attributeencodings', []); + $dstEncodings = $spMetadata->getArray('attributeencodings', []); + + /* + * Merge the two encoding arrays. Encodings specified in the target metadata + * takes precedence over the source metadata. + */ + $encodings = array_merge($srcEncodings, $dstEncodings); + + $ret = []; + foreach ($attributes as $name => $values) { + $ret[$name] = []; + if (array_key_exists($name, $encodings)) { + $encoding = $encodings[$name]; + } else { + $encoding = $defaultEncoding; + } + + foreach ($values as $value) { + // allow null values + if ($value === null) { + $ret[$name][] = $value; + continue; + } + + $attrval = $value; + if ($value instanceof DOMNodeList) { + /** @psalm-suppress PossiblyNullPropertyFetch */ + $attrval = new AttributeValue($value->item(0)->parentNode); + } + + switch ($encoding) { + case 'string': + $value = (string) $attrval; + break; + case 'base64': + $value = base64_encode((string) $attrval); + break; + case 'raw': + if (is_string($value)) { + $doc = DOMDocumentFactory::fromString('' . $value . ''); + $value = $doc->firstChild->childNodes; + } + assert($value instanceof DOMNodeList || $value instanceof NameID); + break; + default: + throw new Error\Exception('Invalid encoding for attribute ' . + var_export($name, true) . ': ' . var_export($encoding, true)); + } + $ret[$name][] = $value; + } + } + + return $ret; + } + + + /** + * Determine which NameFormat we should use for attributes. + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param \SimpleSAML\Configuration $spMetadata The metadata of the SP. + * + * @return string The NameFormat. + */ + private static function getAttributeNameFormat( + Configuration $idpMetadata, + Configuration $spMetadata + ): string { + // try SP metadata first + $attributeNameFormat = $spMetadata->getString('attributes.NameFormat', null); + if ($attributeNameFormat !== null) { + return $attributeNameFormat; + } + $attributeNameFormat = $spMetadata->getString('AttributeNameFormat', null); + if ($attributeNameFormat !== null) { + return $attributeNameFormat; + } + + // look in IdP metadata + $attributeNameFormat = $idpMetadata->getString('attributes.NameFormat', null); + if ($attributeNameFormat !== null) { + return $attributeNameFormat; + } + $attributeNameFormat = $idpMetadata->getString('AttributeNameFormat', null); + if ($attributeNameFormat !== null) { + return $attributeNameFormat; + } + + // default + return Constants::NAMEFORMAT_BASIC; + } + + + /** + * Build an assertion based on information in the metadata. + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param \SimpleSAML\Configuration $spMetadata The metadata of the SP. + * @param array &$state The state array with information about the request. + * + * @return \SAML2\Assertion The assertion. + * + * @throws \SimpleSAML\Error\Exception In case an error occurs when creating a holder-of-key assertion. + */ + private static function buildAssertion( + Configuration $idpMetadata, + Configuration $spMetadata, + array &$state + ): Assertion { + assert(isset($state['Attributes'])); + assert(isset($state['saml:ConsumerURL'])); + + $now = time(); + + $signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', null); + if ($signAssertion === null) { + $signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', true); + } + + $config = Configuration::getInstance(); + + $a = new Assertion(); + if ($signAssertion) { + \SimpleSAML\Module\saml\Message::addSign($idpMetadata, $spMetadata, $a); + } + + $issuer = new Issuer(); + $issuer->setValue($idpMetadata->getString('entityid')); + $issuer->setFormat(Constants::NAMEID_ENTITY); + $a->setIssuer($issuer); + + $audience = array_merge([$spMetadata->getString('entityid')], $spMetadata->getArray('audience', [])); + $a->setValidAudiences($audience); + + $a->setNotBefore($now - 30); + + $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null); + if ($assertionLifetime === null) { + $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); + } + $a->setNotOnOrAfter($now + $assertionLifetime); + + if (isset($state['saml:AuthnContextClassRef'])) { + $a->setAuthnContextClassRef($state['saml:AuthnContextClassRef']); + } elseif (Utils\HTTP::isHTTPS()) { + $a->setAuthnContextClassRef(Constants::AC_PASSWORD_PROTECTED_TRANSPORT); + } else { + $a->setAuthnContextClassRef(Constants::AC_PASSWORD); + } + + $sessionStart = $now; + if (isset($state['AuthnInstant'])) { + $a->setAuthnInstant($state['AuthnInstant']); + $sessionStart = $state['AuthnInstant']; + } + + $sessionLifetime = $config->getInteger('session.duration', 8 * 60 * 60); + $a->setSessionNotOnOrAfter($sessionStart + $sessionLifetime); + + $a->setSessionIndex(Utils\Random::generateID()); + + $sc = new SubjectConfirmation(); + $scd = new SubjectConfirmationData(); + $scd->setNotOnOrAfter($now + $assertionLifetime); + $scd->setRecipient($state['saml:ConsumerURL']); + $scd->setInResponseTo($state['saml:RequestId']); + $sc->setSubjectConfirmationData($scd); + + // ProtcolBinding of SP's overwrites IdP hosted metadata configuration + $hokAssertion = null; + if ($state['saml:Binding'] === Constants::BINDING_HOK_SSO) { + $hokAssertion = true; + } + if ($hokAssertion === null) { + $hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', false); + } + + if ($hokAssertion) { + // Holder-of-Key + $sc->setMethod(Constants::CM_HOK); + if (Utils\HTTP::isHTTPS()) { + if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) { + // extract certificate data (if this is a certificate) + $clientCert = $_SERVER['SSL_CLIENT_CERT']; + $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; + if (preg_match($pattern, $clientCert, $matches)) { + // we have a client certificate from the browser which we add to the HoK assertion + $x509Certificate = new X509Certificate(); + $x509Certificate->setCertificate(str_replace(["\r", "\n", " "], '', $matches[1])); + + $x509Data = new X509Data(); + $x509Data->addData($x509Certificate); + + $keyInfo = new KeyInfo(); + $keyInfo->addInfo($x509Data); + + $scd->addInfo($keyInfo); + } else { + throw new Error\Exception( + 'Error creating HoK assertion: No valid client certificate provided during ' + . 'TLS handshake with IdP' + ); + } + } else { + throw new Error\Exception( + 'Error creating HoK assertion: No client certificate provided during TLS handshake with IdP' + ); + } + } else { + throw new Error\Exception( + 'Error creating HoK assertion: No HTTPS connection to IdP, but required for Holder-of-Key SSO' + ); + } + } else { + // Bearer + $sc->setMethod(Constants::CM_BEARER); + } + $sc->setSubjectConfirmationData($scd); + $a->setSubjectConfirmation([$sc]); + + // add attributes + if ($spMetadata->getBoolean('simplesaml.attributes', true)) { + $attributeNameFormat = self::getAttributeNameFormat($idpMetadata, $spMetadata); + $a->setAttributeNameFormat($attributeNameFormat); + $attributes = self::encodeAttributes($idpMetadata, $spMetadata, $state['Attributes']); + $a->setAttributes($attributes); + } + + $nameIdFormat = null; + + // generate the NameID for the assertion + if (isset($state['saml:NameIDFormat'])) { + $nameIdFormat = $state['saml:NameIDFormat']; + } + + if ($nameIdFormat === null || !isset($state['saml:NameID'][$nameIdFormat])) { + // either not set in request, or not set to a format we supply. Fall back to old generation method + $nameIdFormat = current($spMetadata->getArrayizeString('NameIDFormat', [])); + if ($nameIdFormat === false) { + $nameIdFormat = current($idpMetadata->getArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT])); + } + } + + if (isset($state['saml:NameID'][$nameIdFormat])) { + $nameId = $state['saml:NameID'][$nameIdFormat]; + $nameId->setFormat($nameIdFormat); + } else { + $spNameQualifier = $spMetadata->getString('SPNameQualifier', null); + if ($spNameQualifier === null) { + $spNameQualifier = $spMetadata->getString('entityid'); + } + + if ($nameIdFormat === Constants::NAMEID_TRANSIENT) { + // generate a random id + $nameIdValue = Utils\Random::generateID(); + } else { + /* this code will end up generating either a fixed assigned id (via nameid.attribute) + or random id if not assigned/configured */ + $nameIdValue = self::generateNameIdValue($idpMetadata, $spMetadata, $state); + if ($nameIdValue === null) { + Logger::warning('Falling back to transient NameID.'); + $nameIdFormat = Constants::NAMEID_TRANSIENT; + $nameIdValue = Utils\Random::generateID(); + } + } + + $nameId = new NameID(); + $nameId->setFormat($nameIdFormat); + $nameId->setValue($nameIdValue); + $nameId->setSPNameQualifier($spNameQualifier); + } + + $state['saml:idp:NameID'] = $nameId; + + $a->setNameId($nameId); + + $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null); + if ($encryptNameId === null) { + $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false); + } + if ($encryptNameId) { + $a->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($spMetadata)); + } + + return $a; + } + + + /** + * Encrypt an assertion. + * + * This function takes in a \SAML2\Assertion and encrypts it if encryption of + * assertions are enabled in the metadata. + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param \SimpleSAML\Configuration $spMetadata The metadata of the SP. + * @param \SAML2\Assertion $assertion The assertion we are encrypting. + * + * @return \SAML2\Assertion|\SAML2\EncryptedAssertion The assertion. + * + * @throws \SimpleSAML\Error\Exception In case the encryption key type is not supported. + */ + private static function encryptAssertion( + Configuration $idpMetadata, + Configuration $spMetadata, + Assertion $assertion + ) { + $encryptAssertion = $spMetadata->getBoolean('assertion.encryption', null); + if ($encryptAssertion === null) { + $encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', false); + } + if (!$encryptAssertion) { + // we are _not_ encrypting this assertion, and are therefore done + return $assertion; + } + + + $sharedKey = $spMetadata->getString('sharedkey', null); + if ($sharedKey !== null) { + $algo = $spMetadata->getString('sharedkey_algorithm', null); + if ($algo === null) { + $algo = $idpMetadata->getString('sharedkey_algorithm'); + } + + $key = new XMLSecurityKey($algo); + $key->loadKey($sharedKey); + } else { + $keys = $spMetadata->getPublicKeys('encryption', true); + if (!empty($keys)) { + $key = $keys[0]; + switch ($key['type']) { + case 'X509Certificate': + $pemKey = "-----BEGIN CERTIFICATE-----\n" . + chunk_split($key['X509Certificate'], 64) . + "-----END CERTIFICATE-----\n"; + break; + default: + throw new Error\Exception('Unsupported encryption key type: ' . $key['type']); + } + + // extract the public key from the certificate for encryption + $key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, ['type' => 'public']); + $key->loadKey($pemKey); + } else { + throw new Error\ConfigurationError( + 'Missing encryption key for entity `' . $spMetadata->getString('entityid') . '`', + $spMetadata->getString('metadata-set') . '.php', + null + ); + } + } + + $ea = new EncryptedAssertion(); + $ea->setAssertion($assertion, $key); + return $ea; + } + + + /** + * Build a logout request based on information in the metadata. + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param \SimpleSAML\Configuration $spMetadata The metadata of the SP. + * @param array $association The SP association. + * @param string|null $relayState An id that should be carried across the logout. + * + * @return \SAML2\LogoutRequest The corresponding SAML2 logout request. + */ + private static function buildLogoutRequest( + Configuration $idpMetadata, + Configuration $spMetadata, + array $association, + string $relayState = null + ): LogoutRequest { + $lr = \SimpleSAML\Module\saml\Message::buildLogoutRequest($idpMetadata, $spMetadata); + $lr->setRelayState($relayState); + $lr->setSessionIndex($association['saml:SessionIndex']); + $lr->setNameId($association['saml:NameID']); + + $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null); + if ($assertionLifetime === null) { + $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300); + } + $lr->setNotOnOrAfter(time() + $assertionLifetime); + + $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null); + if ($encryptNameId === null) { + $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false); + } + if ($encryptNameId) { + $lr->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($spMetadata)); + } + + return $lr; + } + + + /** + * Build a authentication response based on information in the metadata. + * + * @param \SimpleSAML\Configuration $idpMetadata The metadata of the IdP. + * @param \SimpleSAML\Configuration $spMetadata The metadata of the SP. + * @param string $consumerURL The Destination URL of the response. + * + * @return \SAML2\Response The SAML2 Response corresponding to the given data. + */ + private static function buildResponse( + Configuration $idpMetadata, + Configuration $spMetadata, + string $consumerURL + ): Response { + $signResponse = $spMetadata->getBoolean('saml20.sign.response', null); + if ($signResponse === null) { + $signResponse = $idpMetadata->getBoolean('saml20.sign.response', true); + } + + $r = new Response(); + $issuer = new Issuer(); + $issuer->setValue($idpMetadata->getString('entityid')); + $issuer->setFormat(Constants::NAMEID_ENTITY); + $r->setIssuer($issuer); + $r->setDestination($consumerURL); + + if ($signResponse) { + \SimpleSAML\Module\saml\Message::addSign($idpMetadata, $spMetadata, $r); + } + + return $r; + } +} \ No newline at end of file diff --git a/modules/sildisco/lib/IdPDisco.php b/modules/sildisco/lib/IdPDisco.php new file mode 100644 index 00000000..0ea08a17 --- /dev/null +++ b/modules/sildisco/lib/IdPDisco.php @@ -0,0 +1,214 @@ +session->getData($sessionDataType, $sessionKeyForSP); */ + public static $sessionDataType = 'sildisco:authentication'; + public static $sessionKeyForSP = 'spentityid'; + + + /** + * Log a message. + * + * This is an helper function for logging messages. It will prefix the messages with our discovery service type. + * + * @param string $message The message which should be logged. + */ + protected function log($message) + { + \SimpleSAML\Logger::info('SildiscoIdPDisco.'.$this->instance.': '.$message); + } + + /* Path to the folder with the SP and IdP metadata */ + private function getMetadataPath() { + return __DIR__ . '/../../../metadata/'; + } + + private function getSPEntityIDAndReducedIdpList() + { + + $idpList = $this->getIdPList(); + $idpList = $this->filterList($idpList); + + $spEntityId = $this->session->getData(self::$sessionDataType, self::$sessionKeyForSP); + + $idpList = DiscoUtils::getReducedIdpList( + $idpList, + $this->getMetadataPath(), + $spEntityId + ); + + return array($spEntityId, self::enableBetaEnabled($idpList)); + } + + /** + * Handles a request to this discovery service. + * + * The IdP disco parameters should be set before calling this function. + */ + public function handleRequest() + { + + $this->start(); + list($spEntityId, $idpList) = $this->getSPEntityIDAndReducedIdpList(); + + if (sizeof($idpList) == 1) { + $idp = array_keys($idpList)[0]; + $idp = $this->validateIdP($idp); + if ($idp !== null) { + + $this->log( + 'Choice made [' . $idp . '] (Redirecting the user back. returnIDParam=' . + $this->returnIdParam . ')' + ); + + \SimpleSAML\Utils\HTTP::redirectTrustedURL( + $this->returnURL, + array($this->returnIdParam => $idp) + ); + } + } + + // Get the SP's name + $spEntries = Metadata::getSpMetadataEntries($this->getMetadataPath()); + + $t = new \SimpleSAML\XHTML\Template($this->config, 'selectidp-links.php', 'disco'); + + $spName = null; + + $rawSPName = $spEntries[$spEntityId][self::$spNameMdKey] ?? null; + if ($rawSPName !== null) { + $spName = htmlspecialchars($t->getTranslator()->getPreferredTranslation( + \SimpleSAML\Utils\Arrays::arrayize($rawSPName, 'en') + )) ; + } + + $t->data['idplist'] = $idpList; + $t->data['return'] = $this->returnURL; + $t->data['returnIDParam'] = $this->returnIdParam; + $t->data['entityID'] = $this->spEntityId; + $t->data['spName'] = $spName; + $t->data['urlpattern'] = htmlspecialchars(\SimpleSAML\Utils\HTTP::getSelfURLNoQuery()); + $t->data['announcement'] = AnnouncementUtils::getSimpleAnnouncement(); + $t->data['helpCenterUrl'] = $this->config->getValue('helpCenterUrl', ''); + + $t->show(); + } + + /** + * @param array $idpList the IDPs with their metadata + * @param bool $isBetaTester optional (default=null) just for unit testing + * @return array $idpList + * + * If the current user has the beta_tester cookie, then for each IDP in + * the idpList that has 'betaEnabled' => true, give it 'enabled' => true + * + */ + public static function enableBetaEnabled($idpList, $isBetaTester=null) { + + if ( $isBetaTester === null) { + $session = \SimpleSAML\Session::getSessionFromRequest(); + $isBetaTester = $session->getData( + self::$sessionType, + self::$betaTesterSessionKey + ); + } + + if ( ! $isBetaTester) { + return $idpList; + } + + foreach ($idpList as $idp => $idpMetadata) { + if ( ! empty($idpMetadata[self::$betaEnabledMdKey])) { + $idpMetadata[self::$enabledMdKey] = true; + $idpList[$idp] = $idpMetadata; + } + } + + return $idpList; + } + + /** + * Validates the given IdP entity id. + * + * Takes a string with the IdP entity id, and returns the entity id if it is valid, or + * null if not. Ensures that the selected IdP is allowed for the current SP + * + * @param string|null $idp The entity id we want to validate. This can be null, in which case we will return null. + * + * @return string|null The entity id if it is valid, null if not. + */ + protected function validateIdP($idp) + { + if ($idp === null) { + return null; + } + if (!$this->config->getBoolean('idpdisco.validate', true)) { + return $idp; + } + + list($spEntityId, $idpList) = $this->getSPEntityIDAndReducedIdpList(); + + /* + * All this complication is for security. + * Without it a user is able to use his authentication through an + * IdP to login to an SP that normally shouldn't accept that IdP. + * + * With a good process, the current SP's entity ID will appear in the + * session and in the request's 'return' entry. + * + * With a hacked process, the SP in the session will not appear in the + * request's 'return' entry. + */ + $returnKey = 'return'; + $requestReturn = array_key_exists($returnKey, $_REQUEST) ? + urldecode(urldecode($_REQUEST[$returnKey])) : ""; + + $spEntityIdParam = 'spentityid='.$spEntityId; + + if (strpos($requestReturn, $spEntityIdParam) === false) { + $message = 'Invalid SP entity id [' . $spEntityId . ']. ' . + 'Could not find in return value. ' . PHP_EOL . $requestReturn; + $this->log($message); + return null; + } + + + if (array_key_exists($idp, $idpList) && $idpList[$idp]['enabled']) { + return $idp; + } + $this->log('Invalid IdP entity id ['.$idp.'] received from discovery page.'); + // the entity id wasn't valid + return null; + } +} diff --git a/modules/sildisco/lib/SSOService.php b/modules/sildisco/lib/SSOService.php new file mode 100644 index 00000000..45d972dd --- /dev/null +++ b/modules/sildisco/lib/SSOService.php @@ -0,0 +1,47 @@ + + * @package SimpleSAMLphp + */ + +require_once('../../_include.php'); + +\SimpleSAML\Logger::info('SAML2.0 - IdP.SSOService: Accessing SAML 2.0 IdP endpoint SSOService'); + +$metadata = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler(); + +$config = \SimpleSAML\Configuration::getInstance(); +if (!$config->getBoolean('enable.saml20-idp', false) || !\SimpleSAML\Module::isModuleEnabled('saml')) { + throw new \SimpleSAML\Error\Error('NOACCESS', null, 403); +} + +$idpEntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); +$idp = \SimpleSAML\IdP::getById('saml2:' . $idpEntityId); + +$hubModeKey = 'hubmode'; + +try { +// If in hub mode, then use the sildisco entry script + if ($config->getValue($hubModeKey, false)) { + \SimpleSAML\Module\sildisco\IdP\SAML2::receiveAuthnRequest($idp); + } else { + \SimpleSAML\Module\saml\IdP\SAML2::receiveAuthnRequest($idp); + } +} catch (\Exception $e) { + if ($e->getMessage() === "Unable to find the current binding.") { + throw new \SimpleSAML\Error\Error('SSOPARAMS', $e, 400); + } else { + throw $e; // do not ignore other exceptions! + } +} +assert(false); diff --git a/modules/sildisco/tests/AddIdpTest.php b/modules/sildisco/tests/AddIdpTest.php new file mode 100644 index 00000000..40ffcd20 --- /dev/null +++ b/modules/sildisco/tests/AddIdpTest.php @@ -0,0 +1,128 @@ + $idp, + 'saml:sp:NameID' => [ + [ + 'Format' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', + 'Value' => 'Tester1_Smith', + 'SPNameQualifier' => 'http://ssp-sp1.local', + ], + ], + 'Attributes' => [], + 'metadataPath' => __DIR__ . '/fixtures/metadata/', + ]; + } + + /** + * Helper function to run the filter with a given configuration. + * + * @param array $config The filter configuration. + * @param array $request The request state. + * @return array The state array after processing. + */ + private static function processAddIdp2NameId(array $config, array $request) + { + $filter = new \SimpleSAML\Module\sildisco\Auth\Process\AddIdp2NameId($config, NULL); + $filter->process($request); + return $request; + } + + /* + * Test with IdP metadata not having an IDPNamespace entry + * @expectedException \SimpleSAML\Error\Exception + */ + public function testAddIdp2NameId_NoIDPNamespace() + { + $this->setExpectedException('\SimpleSAML\Error\Exception'); + $config = [ 'test' => ['value1', 'value2'], ]; + $request = self::getNameID('idp-bare'); + + self::processAddIdp2NameId($config, $request); + } + + + /* + * Test with IdP metadata not having an IDPNamespace entry + * @expectedException \SimpleSAML\Error\Exception + */ + public function testAddIdp2NameId_EmptyIDPNamespace() + { + $this->setExpectedException('\SimpleSAML\Error\Exception'); + $config = [ 'test' => ['value1', 'value2'], ]; + $request = self::getNameID('idp-empty'); + self::processAddIdp2NameId($config, $request); + } + + /* + * Test with IdP metadata not having an IDPNamespace entry + * @expectedException \SimpleSAML\Error\Exception + */ + public function testAddIdp2NameId_BadIDPNamespace() + { + $this->setExpectedException('\SimpleSAML\Error\Exception'); + $config = [ + 'test' => ['value1', 'value2'], + ]; + $request = self::getNameID('idp-bad'); + self::processAddIdp2NameId($config, $request); + } + + + + /* + * Test with IdP metadata having a good IDPNamespace entry + */ + public function testAddIdp2NameId_GoodString() + { + $config = ['test' => ['value1', 'value2']]; + $state = [ + 'saml:sp:IdP' => 'idp-good', + 'saml:sp:NameID' => 'Tester1_SmithA', + 'Attributes' => [], + 'metadataPath' => __DIR__ . '/fixtures/metadata/', + ]; + + $newNameID = $state['saml:sp:NameID']; + $newNameID = 'Tester1_SmithA@idpGood'; + + $expected = $state; + $expected['saml:NameID']['urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'] = $newNameID; + + $results = self::processAddIdp2NameId($config, $state); + $this->assertEquals($expected, $results); + } + /* + * Test with IdP metadata having a good IDPNamespace entry + */ + public function testAddIdp2NameId_GoodArray() + { + $config = ['test' => ['value1', 'value2']]; + $state = [ + 'saml:sp:IdP' => 'idp-good', + 'saml:sp:NameID' => [ + 'Format' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:transient', + 'Value' => 'Tester1_SmithA', + 'SPNameQualifier' => 'http://ssp-sp1.local', + ], + 'Attributes' => [], + 'metadataPath' => __DIR__ . '/fixtures/metadata/', + ]; + + $newNameID = $state['saml:sp:NameID']; + $newNameID['Value'] = 'Tester1_SmithA@idpGood'; + + $expected = $state; + $expected['saml:NameID']['urn:oasis:names:tc:SAML:1.1:nameid-format:transient'] = $newNameID; + + $results = self::processAddIdp2NameId($config, $state); + + $this->assertEquals($expected, $results); + } + +} diff --git a/modules/sildisco/tests/TagGroupTest.php b/modules/sildisco/tests/TagGroupTest.php new file mode 100644 index 00000000..f00a601c --- /dev/null +++ b/modules/sildisco/tests/TagGroupTest.php @@ -0,0 +1,106 @@ +process($request); + return $request; + } + + /* + * Test with oid and friendly keys for groups + * @expectedException \SimpleSAML\Error\Exception + */ + public function testTagGroup_Both() + { + $config = [ 'test' => ['value1', 'value2'], ]; + $request = [ + "saml:sp:IdP" => 'idp-bare', + "Attributes" => [ + 'urn:oid:2.5.4.31' => ['ADMINS'], + 'member' => ['ADMINS'], + ], + 'metadataPath' => __DIR__ . '/fixtures/metadata/', + ]; + + $expected = $request; + $expected["Attributes"]['urn:oid:2.5.4.31'] = ['idp|idp-bare|ADMINS']; + $expected["Attributes"]['member'] = ['idp|idp-bare|ADMINS']; + $results = self::processTagGroup($config, $request); + $this->assertEquals($expected, $results); + } + + + /* + * Test with friendly key for groups + * @expectedException \SimpleSAML\Error\Exception + */ + public function testTagGroup_Member() + { + $config = [ 'test' => ['value1', 'value2'], ]; + $request = [ + "saml:sp:IdP" => 'idp-bare', + "Attributes" => [ + 'member' => ['ADMINS'], + ], + 'metadataPath' => __DIR__ . '/fixtures/metadata/', + ]; + + $expected = $request; + $expected["Attributes"]['member'] = ['idp|idp-bare|ADMINS']; + $results = self::processTagGroup($config, $request); + $this->assertEquals($expected, $results); + } + + /* + * Test with oid key for groups + * @expectedException \SimpleSAML\Error\Exception + */ + public function testTagGroup_Oid() + { + $config = [ 'test' => ['value1', 'value2'], ]; + $request = [ + "saml:sp:IdP" => 'idp-bare', + "Attributes" => [ + 'urn:oid:2.5.4.31' => ['ADMINS'], + ], + 'metadataPath' => __DIR__ . '/fixtures/metadata/', + ]; + + $expected = $request; + $expected["Attributes"]['urn:oid:2.5.4.31'] = ['idp|idp-bare|ADMINS']; + $results = self::processTagGroup($config, $request); + $this->assertEquals($expected, $results); + } + + /* + * Test with oid key for groups + * @expectedException \SimpleSAML\Error\Exception + */ + public function testTagGroup_IdpGood() + { + $config = [ 'test' => ['value1', 'value2'], ]; + $request = [ + "saml:sp:IdP" => 'idp-good', + "Attributes" => [ + 'urn:oid:2.5.4.31' => ['ADMINS'], + ], + 'metadataPath' => __DIR__ . '/fixtures/metadata/', + ]; + + $expected = $request; + $expected["Attributes"]['urn:oid:2.5.4.31'] = ['idp|idpGood|ADMINS']; + $results = self::processTagGroup($config, $request); + $this->assertEquals($expected, $results); + } +} diff --git a/modules/sildisco/tests/fixtures/metadata/idp-bad-code.php b/modules/sildisco/tests/fixtures/metadata/idp-bad-code.php new file mode 100644 index 00000000..efbce0e7 --- /dev/null +++ b/modules/sildisco/tests/fixtures/metadata/idp-bad-code.php @@ -0,0 +1,12 @@ + [ + 'SingleSignOnService' => 'http://idp-empty/saml2/idp/SSOService.php', + 'IDPNamespace' => '', + ], + 'idp-bad' => [ + 'SingleSignOnService' => 'http://idp-bad/saml2/idp/SSOService.php', + 'IDPNamespace' => 'ba!d!', + ], +]; \ No newline at end of file diff --git a/modules/sildisco/tests/fixtures/metadata/idp-bare.php b/modules/sildisco/tests/fixtures/metadata/idp-bare.php new file mode 100644 index 00000000..c2d28c07 --- /dev/null +++ b/modules/sildisco/tests/fixtures/metadata/idp-bare.php @@ -0,0 +1,7 @@ + [ + 'SingleSignOnService' => 'http://idp-bare/saml2/idp/SSOService.php', + ], +]; diff --git a/modules/sildisco/tests/fixtures/metadata/idp-good.php b/modules/sildisco/tests/fixtures/metadata/idp-good.php new file mode 100644 index 00000000..c06cfa22 --- /dev/null +++ b/modules/sildisco/tests/fixtures/metadata/idp-good.php @@ -0,0 +1,8 @@ + [ + 'SingleSignOnService' => 'http://idp-bare/saml2/idp/SSOService.php', + 'IDPNamespace' => 'idpGood', + ], +]; diff --git a/modules/sildisco/tests/phpunit.xml b/modules/sildisco/tests/phpunit.xml new file mode 100644 index 00000000..807ed707 --- /dev/null +++ b/modules/sildisco/tests/phpunit.xml @@ -0,0 +1,30 @@ + + + + + ../tests/ + + + + + ../tests/ + + ../fixtures/ + + + + + + + + + \ No newline at end of file diff --git a/modules/sildisco/www/betatest.php b/modules/sildisco/www/betatest.php new file mode 100644 index 00000000..b4facf87 --- /dev/null +++ b/modules/sildisco/www/betatest.php @@ -0,0 +1,12 @@ +setData($sessionType, $sessionKey, 1, \SimpleSAML\Session::DATA_TIMEOUT_SESSION_END); + +echo "

Start Beta Testing

"; +echo "

You have been given a cookie to allow you to test beta-enabled IDPs.

"; +echo "

To remove the cookie, just close your browser.

"; diff --git a/modules/sildisco/www/disco.php b/modules/sildisco/www/disco.php new file mode 100644 index 00000000..6c3c08f0 --- /dev/null +++ b/modules/sildisco/www/disco.php @@ -0,0 +1,9 @@ +handleRequest(); diff --git a/modules/sildisco/www/metadata.php b/modules/sildisco/www/metadata.php new file mode 100644 index 00000000..d040bdc3 --- /dev/null +++ b/modules/sildisco/www/metadata.php @@ -0,0 +1,224 @@ +getBoolean('enable.saml20-idp', false)) { + throw new \SimpleSAML\Error\Error('NOACCESS'); +} + +// check if valid local session exists +//if ($config->getBoolean('admin.protectmetadata', false)) { +// Auth::requireAdmin(); +//} + +try { + $idpentityid = isset($_GET['idpentityid']) ? + $_GET['idpentityid'] : + $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); + $idpmeta = $metadata->getMetaDataConfig($idpentityid, 'saml20-idp-hosted'); + + $availableCerts = array(); + + $keys = array(); + $certInfo = Crypto::loadPublicKey($idpmeta, false, 'new_'); + if ($certInfo !== null) { + $availableCerts['new_idp.crt'] = $certInfo; + $keys[] = array( + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => true, + 'X509Certificate' => $certInfo['certData'], + ); + $hasNewCert = true; + } else { + $hasNewCert = false; + } + + $certInfo = Crypto::loadPublicKey($idpmeta, true); + $availableCerts['idp.crt'] = $certInfo; + $keys[] = array( + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => ($hasNewCert ? false : true), + 'X509Certificate' => $certInfo['certData'], + ); + + if ($idpmeta->hasValue('https.certificate')) { + $httpsCert = Crypto::loadPublicKey($idpmeta, true, 'https.'); + assert('isset($httpsCert["certData"])'); + $availableCerts['https.crt'] = $httpsCert; + $keys[] = array( + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => false, + 'X509Certificate' => $httpsCert['certData'], + ); + } + + $metaArray = array( + 'metadata-set' => 'saml20-idp-remote', + 'entityid' => $idpentityid, + ); + + $ssob = $metadata->getGenerated('SingleSignOnServiceBinding', 'saml20-idp-hosted'); + $slob = $metadata->getGenerated('SingleLogoutServiceBinding', 'saml20-idp-hosted'); + $ssol = $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted'); + $slol = $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted'); + + if (is_array($ssob)) { + foreach ($ssob as $binding) { + $metaArray['SingleSignOnService'][] = array( + 'Binding' => $binding, + 'Location' => $ssol, + ); + } + } else { + $metaArray['SingleSignOnService'][] = array( + 'Binding' => $ssob, + 'Location' => $ssol, + ); + } + + if (is_array($slob)) { + foreach ($slob as $binding) { + $metaArray['SingleLogoutService'][] = array( + 'Binding' => $binding, + 'Location' => $slol, + ); + } + } else { + $metaArray['SingleLogoutService'][] = array( + 'Binding' => $slob, + 'Location' => $slol, + ); + } + + if (count($keys) === 1) { + $metaArray['certData'] = $keys[0]['X509Certificate']; + } else { + $metaArray['keys'] = $keys; + } + + if ($idpmeta->getBoolean('saml20.sendartifact', false)) { + // Artifact sending enabled + $metaArray['ArtifactResolutionService'][] = array( + 'index' => 0, + 'Location' => HTTP::getBaseURL().'saml2/idp/ArtifactResolutionService.php', + 'Binding' => Constants::BINDING_SOAP, + ); + } + + if ($idpmeta->getBoolean('saml20.hok.assertion', false)) { + // Prepend HoK SSO Service endpoint. + array_unshift($metaArray['SingleSignOnService'], array( + 'hoksso:ProtocolBinding' => Constants::BINDING_HTTP_REDIRECT, + 'Binding' => Constants::BINDING_HOK_SSO, + 'Location' => HTTP::getBaseURL().'saml2/idp/SSOService.php' + )); + } + + $metaArray['NameIDFormat'] = $idpmeta->getString( + 'NameIDFormat', + 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' + ); + + if ($idpmeta->hasValue('OrganizationName')) { + $metaArray['OrganizationName'] = $idpmeta->getLocalizedString('OrganizationName'); + $metaArray['OrganizationDisplayName'] = $idpmeta->getLocalizedString( + 'OrganizationDisplayName', + $metaArray['OrganizationName'] + ); + + if (!$idpmeta->hasValue('OrganizationURL')) { + throw new \SimpleSAML\Error\Exception('If OrganizationName is set, OrganizationURL must also be set.'); + } + $metaArray['OrganizationURL'] = $idpmeta->getLocalizedString('OrganizationURL'); + } + + if ($idpmeta->hasValue('scope')) { + $metaArray['scope'] = $idpmeta->getArray('scope'); + } + + if ($idpmeta->hasValue('EntityAttributes')) { + $metaArray['EntityAttributes'] = $idpmeta->getArray('EntityAttributes'); + + // check for entity categories + if (Metadata::isHiddenFromDiscovery($metaArray)) { + $metaArray['hide.from.discovery'] = true; + } + } + + if ($idpmeta->hasValue('UIInfo')) { + $metaArray['UIInfo'] = $idpmeta->getArray('UIInfo'); + } + + if ($idpmeta->hasValue('DiscoHints')) { + $metaArray['DiscoHints'] = $idpmeta->getArray('DiscoHints'); + } + + if ($idpmeta->hasValue('RegistrationInfo')) { + $metaArray['RegistrationInfo'] = $idpmeta->getArray('RegistrationInfo'); + } + + if ($idpmeta->hasValue('validate.authnrequest')) { + $metaArray['sign.authnrequest'] = $idpmeta->getBoolean('validate.authnrequest'); + } + + if ($idpmeta->hasValue('redirect.validate')) { + $metaArray['redirect.sign'] = $idpmeta->getBoolean('redirect.validate'); + } + + if ($idpmeta->hasValue('contacts')) { + $contacts = $idpmeta->getArray('contacts'); + foreach ($contacts as $contact) { + $metaArray['contacts'][] = Metadata::getContact($contact); + } + } + + $technicalContactEmail = $config->getString('technicalcontact_email', false); + if ($technicalContactEmail && $technicalContactEmail !== 'na@example.org') { + $techcontact['emailAddress'] = $technicalContactEmail; + $techcontact['name'] = $config->getString('technicalcontact_name', null); + $techcontact['contactType'] = 'technical'; + $metaArray['contacts'][] = Metadata::getContact($techcontact); + } + + $metaBuilder = new \SimpleSAML\Metadata\SAMLBuilder($idpentityid); + $metaBuilder->addMetadataIdP20($metaArray); + $metaBuilder->addOrganizationInfo($metaArray); + + $metaxml = $metaBuilder->getEntityDescriptorText(); + + $metaflat = '$metadata['.var_export($idpentityid, true).'] = '.var_export($metaArray, true).';'; + + // sign the metadata if enabled + $metaxml = \SimpleSAML\Metadata\Signer::sign($metaxml, $idpmeta->toArray(), 'SAML 2 IdP'); + + if (array_key_exists('format', $_GET) && $_GET['format'] == 'xml') { + header('Content-Type: application/xml'); + + echo $metaxml; + exit(0); + } else { + + header('Content-Type: text/html; charset=utf-8'); + + echo '
' . print_r($metaflat, true) . '
'; + exit(0); + } +} catch (Exception $exception) { + throw new \SimpleSAML\Error\Error('METADATA', $exception); +} diff --git a/modules/sildisco/www/sp/discoresp.php b/modules/sildisco/www/sp/discoresp.php new file mode 100644 index 00000000..449309ff --- /dev/null +++ b/modules/sildisco/www/sp/discoresp.php @@ -0,0 +1,34 @@ +startSSO($_REQUEST['idpentityid'], $state); diff --git a/modules/sildisco/www/sp/saml2-acs.php b/modules/sildisco/www/sp/saml2-acs.php new file mode 100644 index 00000000..d5d29aca --- /dev/null +++ b/modules/sildisco/www/sp/saml2-acs.php @@ -0,0 +1,273 @@ +getMetadata(); +try { + $b = \SAML2\Binding::getCurrentBinding(); +} catch (Exception $e) { + // TODO: look for a specific exception + // This is dirty. Instead of checking the message of the exception, \SAML2\Binding::getCurrentBinding() should throw + // a specific exception when the binding is unknown, and we should capture that here + if ($e->getMessage() === 'Unable to find the current binding.') { + throw new \SimpleSAML\Error\Error('ACSPARAMS', $e, 400); + } else { + // do not ignore other exceptions! + throw $e; + } +} + +if ($b instanceof \SAML2\HTTPArtifact) { + $b->setSPMetadata($spMetadata); +} + +$response = $b->receive(); +if (!($response instanceof \SAML2\Response)) { + throw new \SimpleSAML\Error\BadRequest('Invalid message received to AssertionConsumerService endpoint.'); +} + +/** @psalm-var null|string|\SAML2\XML\saml\Issuer $issuer Remove in SSP 2.0 */ +$issuer = $response->getIssuer(); +if ($issuer === null) { + // no Issuer in the response. Look for an unencrypted assertion with an issuer + foreach ($response->getAssertions() as $a) { + if ($a instanceof \SAML2\Assertion) { + // we found an unencrypted assertion, there should be an issuer here + $issuer = $a->getIssuer(); + break; + } + } + /** @psalm-var string|null $issuer Remove in SSP 2.0 */ + if ($issuer === null) { + // no issuer found in the assertions + throw new Exception('Missing in message delivered to AssertionConsumerService.'); + } +} + +if ($issuer instanceof \SAML2\XML\saml\Issuer) { + /** @psalm-var string|null $issuer */ + $issuer = $issuer->getValue(); + if ($issuer === null) { + // no issuer found in the assertions + throw new Exception('Missing in message delivered to AssertionConsumerService.'); + } +} + +$session = \SimpleSAML\Session::getSessionFromRequest(); +$prevAuth = $session->getAuthData($sourceId, 'saml:sp:prevAuth'); +/** @psalm-var string $issuer */ +if ($prevAuth !== null && $prevAuth['id'] === $response->getId() && $prevAuth['issuer'] === $issuer) { + /* OK, it looks like this message has the same issuer + * and ID as the SP session we already have active. We + * therefore assume that the user has somehow triggered + * a resend of the message. + * In that case we may as well just redo the previous redirect + * instead of displaying a confusing error message. + */ + SimpleSAML\Logger::info( + 'Duplicate SAML 2 response detected - ignoring the response and redirecting the user to the correct page.' + ); + if (isset($prevAuth['redirect'])) { + \SimpleSAML\Utils\HTTP::redirectTrustedURL($prevAuth['redirect']); + } + + SimpleSAML\Logger::info('No RelayState or ReturnURL available, cannot redirect.'); + throw new \SimpleSAML\Error\Exception('Duplicate assertion received.'); +} + +$idpMetadata = null; +$state = null; +$stateId = $response->getInResponseTo(); + +if (!empty($stateId)) { + // this should be a response to a request we sent earlier + try { + $state = \SimpleSAML\Auth\State::loadState($stateId, 'saml:sp:sso'); + } catch (Exception $e) { + // something went wrong, + SimpleSAML\Logger::warning('Could not load state specified by InResponseTo: ' . $e->getMessage() . + ' Processing response as unsolicited.'); + } +} + +if ($state) { + // check that the authentication source is correct + assert(array_key_exists('saml:sp:AuthId', $state)); + if ($state['saml:sp:AuthId'] !== $sourceId) { + throw new \SimpleSAML\Error\Exception( + 'The authentication source id in the URL does not match the authentication source which sent the request.' + ); + } + + // check that the issuer is the one we are expecting + assert(array_key_exists('ExpectedIssuer', $state)); + if ($state['ExpectedIssuer'] !== $issuer) { + $idpMetadata = $source->getIdPMetadata($issuer); + $idplist = $idpMetadata->getArrayize('IDPList', []); + if (!in_array($state['ExpectedIssuer'], $idplist, true)) { + SimpleSAML\Logger::warning( + 'The issuer of the response not match to the identity provider we sent the request to.' + ); + } + } +} else { + // this is an unsolicited response + $relaystate = $spMetadata->getString('RelayState', $response->getRelayState()); + $state = [ + 'saml:sp:isUnsolicited' => true, + 'saml:sp:AuthId' => $sourceId, + 'saml:sp:RelayState' => $relaystate === null ? null : \SimpleSAML\Utils\HTTP::checkURLAllowed($relaystate), + ]; +} + +SimpleSAML\Logger::debug('Received SAML2 Response from ' . var_export($issuer, true) . '.'); + +if (is_null($idpMetadata)) { + $idpMetadata = $source->getIdPmetadata($issuer); +} + +try { + $assertions = \SimpleSAML\Module\saml\Message::processResponse($spMetadata, $idpMetadata, $response); +} catch (\SimpleSAML\Module\saml\Error $e) { + // the status of the response wasn't "success" + $e = $e->toException(); + \SimpleSAML\Auth\State::throwException($state, $e); +} + +$authenticatingAuthority = null; +$nameId = null; +$sessionIndex = null; +$expire = null; +$attributes = []; +$foundAuthnStatement = false; + +foreach ($assertions as $assertion) { + // check for duplicate assertion (replay attack) + $store = \SimpleSAML\Store::getInstance(); + if ($store !== false) { + $aID = $assertion->getId(); + if ($store->get('saml.AssertionReceived', $aID) !== null) { + $e = new \SimpleSAML\Error\Exception('Received duplicate assertion.'); + \SimpleSAML\Auth\State::throwException($state, $e); + } + + $notOnOrAfter = $assertion->getNotOnOrAfter(); + if ($notOnOrAfter === null) { + $notOnOrAfter = time() + 24 * 60 * 60; + } else { + $notOnOrAfter += 60; // we allow 60 seconds clock skew, so add it here also + } + + $store->set('saml.AssertionReceived', $aID, true, $notOnOrAfter); + } + + if ($authenticatingAuthority === null) { + $authenticatingAuthority = $assertion->getAuthenticatingAuthority(); + } + if ($nameId === null) { + $nameId = $assertion->getNameId(); + } + if ($sessionIndex === null) { + $sessionIndex = $assertion->getSessionIndex(); + } + if ($expire === null) { + $expire = $assertion->getSessionNotOnOrAfter(); + } + + $attributes = array_merge($attributes, $assertion->getAttributes()); + + if ($assertion->getAuthnInstant() !== null) { + // assertion contains AuthnStatement, since AuthnInstant is a required attribute + $foundAuthnStatement = true; + } +} +$assertion = end($assertions); + +if (!$foundAuthnStatement) { + $e = new \SimpleSAML\Error\Exception('No AuthnStatement found in assertion(s).'); + \SimpleSAML\Auth\State::throwException($state, $e); +} + +if ($expire !== null) { + $logoutExpire = $expire; +} else { + // just expire the logout association 24 hours into the future + $logoutExpire = time() + 24 * 60 * 60; +} + +if (!empty($nameId)) { + // register this session in the logout store + \SimpleSAML\Module\saml\SP\LogoutStore::addSession($sourceId, $nameId, $sessionIndex, $logoutExpire); + + // we need to save the NameID and SessionIndex for logout + $logoutState = [ + 'saml:logout:Type' => 'saml2', + 'saml:logout:IdP' => $issuer, + 'saml:logout:NameID' => $nameId, + 'saml:logout:SessionIndex' => $sessionIndex, + ]; + + $state['saml:sp:NameID'] = $nameId; // no need to mark it as persistent, it already is +} else { + /* + * No NameID provided, we can't logout from this IdP! + * + * Even though interoperability profiles "require" a NameID, the SAML 2.0 standard does not require it to be present + * in assertions. That way, we could have a Subject with only a SubjectConfirmation, or even no Subject element at + * all. + * + * In case we receive a SAML assertion with no NameID, we can be graceful and continue, but we won't be able to + * perform a Single Logout since the SAML logout profile mandates the use of a NameID to identify the individual we + * want to be logged out. In order to minimize the impact of this, we keep logout state information (without saving + * it to the store), marking the IdP as SAML 1.0, which does not implement logout. Then we can safely log the user + * out from the local session, skipping Single Logout upstream to the IdP. + */ + $logoutState = [ + 'saml:logout:Type' => 'saml1', + ]; +} + +$state['LogoutState'] = $logoutState; +$state['saml:AuthenticatingAuthority'] = $authenticatingAuthority; +$state['saml:AuthenticatingAuthority'][] = $issuer; +$state['PersistentAuthData'][] = 'saml:AuthenticatingAuthority'; +$state['saml:AuthnInstant'] = $assertion->getAuthnInstant(); +$state['PersistentAuthData'][] = 'saml:AuthnInstant'; +$state['saml:sp:SessionIndex'] = $sessionIndex; +$state['PersistentAuthData'][] = 'saml:sp:SessionIndex'; +$state['saml:sp:AuthnContext'] = $assertion->getAuthnContextClassRef(); +$state['PersistentAuthData'][] = 'saml:sp:AuthnContext'; + +if ($expire !== null) { + $state['Expire'] = $expire; +} + +// note some information about the authentication, in case we receive the same response again +$state['saml:sp:prevAuth'] = [ + 'id' => $response->getId(), + 'issuer' => $issuer, + 'inResponseTo' => $response->getInResponseTo(), +]; +if (isset($state['\SimpleSAML\Auth\Source.ReturnURL'])) { + $state['saml:sp:prevAuth']['redirect'] = $state['\SimpleSAML\Auth\Source.ReturnURL']; +} elseif (isset($state['saml:sp:RelayState'])) { + $state['saml:sp:prevAuth']['redirect'] = $state['saml:sp:RelayState']; +} +$state['PersistentAuthData'][] = 'saml:sp:prevAuth'; + +$source->handleResponse($state, $issuer, $attributes); +assert(false); \ No newline at end of file diff --git a/modules/sildisco/www/sp/saml2-logout.php b/modules/sildisco/www/sp/saml2-logout.php new file mode 100644 index 00000000..05d9c14b --- /dev/null +++ b/modules/sildisco/www/sp/saml2-logout.php @@ -0,0 +1,155 @@ +getMessage() === 'Unable to find the current binding.') { + throw new \SimpleSAML\Error\Error('SLOSERVICEPARAMS', $e, 400); + } else { + throw $e; // do not ignore other exceptions! + } +} +$message = $binding->receive(); + +$issuer = $message->getIssuer(); +if ($issuer instanceof \SAML2\XML\saml\Issuer) { + $idpEntityId = $issuer->getValue(); +} else { + $idpEntityId = $issuer; +} + +if ($idpEntityId === null) { + // Without an issuer we have no way to respond to the message. + throw new \SimpleSAML\Error\BadRequest('Received message on logout endpoint without issuer.'); +} + +/** @var \SimpleSAML\Module\saml\Auth\Source\SP $source */ +$spEntityId = $source->getEntityId(); + +$metadata = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler(); +$idpMetadata = $source->getIdPMetadata($idpEntityId); +$spMetadata = $source->getMetadata(); + +\SimpleSAML\Module\saml\Message::validateMessage($idpMetadata, $spMetadata, $message); + +$destination = $message->getDestination(); +if ($destination !== null && $destination !== \SimpleSAML\Utils\HTTP::getSelfURLNoQuery()) { + throw new \SimpleSAML\Error\Exception('Destination in logout message is wrong.'); +} + +if ($message instanceof \SAML2\LogoutResponse) { + $relayState = $message->getRelayState(); + if ($relayState === null) { + // Somehow, our RelayState has been lost. + throw new \SimpleSAML\Error\BadRequest('Missing RelayState in logout response.'); + } + + if (!$message->isSuccess()) { + \SimpleSAML\Logger::warning( + 'Unsuccessful logout. Status was: ' . \SimpleSAML\Module\saml\Message::getResponseError($message) + ); + } + + $state = \SimpleSAML\Auth\State::loadState($relayState, 'saml:slosent'); + $state['saml:sp:LogoutStatus'] = $message->getStatus(); + \SimpleSAML\Auth\Source::completeLogout($state); +} elseif ($message instanceof \SAML2\LogoutRequest) { + \SimpleSAML\Logger::debug('module/sildisco/sp/logout: Request from ' . $idpEntityId); // GTIS + \SimpleSAML\Logger::stats('saml20-idp-SLO idpinit ' . $spEntityId . ' ' . $idpEntityId); + + if ($message->isNameIdEncrypted()) { + try { + $keys = \SimpleSAML\Module\saml\Message::getDecryptionKeys($idpMetadata, $spMetadata); + } catch (\Exception $e) { + throw new \SimpleSAML\Error\Exception('Error decrypting NameID: ' . $e->getMessage()); + } + + $blacklist = \SimpleSAML\Module\saml\Message::getBlacklistedAlgorithms($idpMetadata, $spMetadata); + + $lastException = null; + foreach ($keys as $i => $key) { + try { + $message->decryptNameId($key, $blacklist); + \SimpleSAML\Logger::debug('Decryption with key #' . $i . ' succeeded.'); + $lastException = null; + break; + } catch (\Exception $e) { + \SimpleSAML\Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage()); + $lastException = $e; + } + } + if ($lastException !== null) { + throw $lastException; + } + } + + $nameId = $message->getNameId(); + $sessionIndexes = $message->getSessionIndexes(); + + /** @psalm-suppress PossiblyNullArgument This will be fixed in saml2 5.0 */ + $numLoggedOut = \SimpleSAML\Module\saml\SP\LogoutStore::logoutSessions($sourceId, $nameId, $sessionIndexes); + if ($numLoggedOut === false) { + // This type of logout was unsupported. Use the old method + $source->handleLogout($idpEntityId); + $numLoggedOut = count($sessionIndexes); + } + + // Create and send response + $lr = \SimpleSAML\Module\saml\Message::buildLogoutResponse($spMetadata, $idpMetadata); + $lr->setRelayState($message->getRelayState()); + $lr->setInResponseTo($message->getId()); + + if ($numLoggedOut < count($sessionIndexes)) { + \SimpleSAML\Logger::warning('Logged out of ' . $numLoggedOut . ' of ' . count($sessionIndexes) . ' sessions.'); + } + + /** @var array $dst */ + $dst = $idpMetadata->getEndpointPrioritizedByBinding( + 'SingleLogoutService', + [ + \SAML2\Constants::BINDING_HTTP_REDIRECT, + \SAML2\Constants::BINDING_HTTP_POST + ] + ); + + if (!($binding instanceof \SAML2\SOAP)) { + $binding = \SAML2\Binding::getBinding($dst['Binding']); + if (isset($dst['ResponseLocation'])) { + $dst = $dst['ResponseLocation']; + } else { + $dst = $dst['Location']; + } + $binding->setDestination($dst); + } + $lr->setDestination($dst); + + $binding->send($lr); +} else { + throw new \SimpleSAML\Error\BadRequest('Unknown message received on logout endpoint: ' . get_class($message)); +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..aca8b163 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "name": "simplesamlphp-module-material", + "dependencies": { + "@simplewebauthn/browser": "^4.1.0" + } +} diff --git a/tests/IdpDiscoTest.php b/tests/IdpDiscoTest.php new file mode 100644 index 00000000..df5e171a --- /dev/null +++ b/tests/IdpDiscoTest.php @@ -0,0 +1,52 @@ +assertEquals($expected, $results); + } + + public function testEnableBetaEnabledNoChange() + { + $isBetaEnabled = 1; + $enabledKey = IdPDisco::$enabledMdKey; + $idpList = [ + 'idp1' => [$enabledKey => false], + 'idp2' => [$enabledKey => true], + ]; + $expected = $idpList; + + $results = IdPDisco::enableBetaEnabled($idpList, $isBetaEnabled); + $this->assertEquals($expected, $results); + } + + public function testEnableBetaEnabledChange() + { + $isBetaEnabled = 1; + $enabledKey = IdPDisco::$enabledMdKey; + $betaEnabledKey = IdPDisco::$betaEnabledMdKey; + $idpList = [ + 'idp1' => [$enabledKey => false], + 'idp2' => [$enabledKey => true, $betaEnabledKey => true], + 'idp3' => [$enabledKey => false, $betaEnabledKey => true], + 'idp4' => [$enabledKey => false, $betaEnabledKey => false], + ]; + $expected = $idpList; + $expected['idp3'][$enabledKey] = true; + + $results = IdPDisco::enableBetaEnabled($idpList, $isBetaEnabled); + $this->assertEquals($expected, $results); + } + +}