From 2d13a24cfd55fb2f4cc87cda24b84a8e1d085e95 Mon Sep 17 00:00:00 2001 From: Jorge Date: Fri, 11 Nov 2011 19:44:04 -0300 Subject: [PATCH] Initial commit --- LICENSE.txt | 175 ++++++++++ README | 23 ++ Readme-Icons.txt | 8 + .../securesocial/SecureSocial.java | 178 ++++++++++ .../UsernamePasswordController.java | 134 +++++++ app/notifiers/securesocial/Mails.java | 50 +++ app/securesocial/jobs/Bootstrap.java | 93 +++++ .../jobs/PendingActivationsJobs.java | 35 ++ .../provider/AccessDeniedException.java | 29 ++ .../provider/AuthenticationException.java | 39 +++ .../provider/AuthenticationMethod.java | 28 ++ .../provider/DefaultUserService.java | 70 ++++ .../provider/IdentityProvider.java | 129 +++++++ app/securesocial/provider/OAuth1Provider.java | 116 ++++++ app/securesocial/provider/OAuth2Provider.java | 155 ++++++++ .../provider/OpenIDOAuthHybridProvider.java | 136 ++++++++ app/securesocial/provider/OpenIDProvider.java | 119 +++++++ .../provider/ProviderRegistry.java | 69 ++++ app/securesocial/provider/ProviderType.java | 32 ++ app/securesocial/provider/SocialUser.java | 90 +++++ app/securesocial/provider/UserId.java | 33 ++ app/securesocial/provider/UserService.java | 139 ++++++++ .../provider/providers/FacebookProvider.java | 61 ++++ .../providers/FoursquareProvider.java | 91 +++++ .../provider/providers/GoogleProvider.java | 70 ++++ .../provider/providers/LinkedInProvider.java | 65 ++++ .../provider/providers/MyOpenIDProvider.java | 58 +++ .../provider/providers/TwitterProvider.java | 56 +++ .../providers/UsernamePasswordProvider.java | 101 ++++++ .../provider/providers/WordpressProvider.java | 89 +++++ .../provider/providers/YahooProvider.java | 58 +++ .../Mails/sendActivationEmail.html | 6 + .../securesocial/SecureSocial/login.html | 96 +++++ app/views/securesocial/SecureSocial/main.html | 30 ++ .../securesocial/SecureSocial/noticePage.html | 16 + .../UsernamePasswordController/signup.html | 83 +++++ build.xml | 49 +++ commands.py | 35 ++ commands.pyc | Bin 0 -> 1100 bytes conf/dependencies.yml | 4 + conf/messages | 47 +++ conf/routes | 25 ++ documentation/images/loginPage.png | Bin 0 -> 46762 bytes documentation/images/signUpPage.png | Bin 0 -> 38133 bytes documentation/manual/home.textile | 247 +++++++++++++ public/images/providers/facebook.png | Bin 0 -> 939 bytes public/images/providers/foursquare.png | Bin 0 -> 2001 bytes public/images/providers/google.png | Bin 0 -> 1474 bytes public/images/providers/linkedin.png | Bin 0 -> 1020 bytes public/images/providers/myopenid.png | Bin 0 -> 4117 bytes public/images/providers/twitter.png | Bin 0 -> 946 bytes public/images/providers/wordpress.png | Bin 0 -> 1757 bytes public/images/providers/yahoo.png | Bin 0 -> 1390 bytes .../bootstrap-1.3.0/bootstrap.min.css | 330 ++++++++++++++++++ .../testapp/app/controllers/Application.java | 12 + .../testapp/app/views/Application/index.html | 35 ++ .../testapp/app/views/errors/404.html | 19 + .../testapp/app/views/errors/500.html | 21 ++ samples-and-tests/testapp/app/views/main.html | 31 ++ .../testapp/conf/application.conf | 296 ++++++++++++++++ .../testapp/conf/dependencies.yml | 12 + samples-and-tests/testapp/conf/messages | 3 + samples-and-tests/testapp/conf/routes | 18 + .../testapp/modules/securesocial | 1 + .../testapp/public/images/favicon.png | Bin 0 -> 687 bytes .../public/javascripts/jquery-1.5.2.min.js | 16 + .../bootstrap-1.3.0/bootstrap.min.css | 330 ++++++++++++++++++ .../testapp/public/stylesheets/main.css | 0 .../testapp/test/Application.test.html | 7 + .../testapp/test/ApplicationTest.java | 17 + samples-and-tests/testapp/test/BasicTest.java | 13 + samples-and-tests/testapp/test/data.yml | 7 + src/play.plugins | 0 73 files changed, 4335 insertions(+) create mode 100644 LICENSE.txt create mode 100644 Readme-Icons.txt create mode 100644 app/controllers/securesocial/SecureSocial.java create mode 100644 app/controllers/securesocial/UsernamePasswordController.java create mode 100644 app/notifiers/securesocial/Mails.java create mode 100644 app/securesocial/jobs/Bootstrap.java create mode 100644 app/securesocial/jobs/PendingActivationsJobs.java create mode 100644 app/securesocial/provider/AccessDeniedException.java create mode 100644 app/securesocial/provider/AuthenticationException.java create mode 100644 app/securesocial/provider/AuthenticationMethod.java create mode 100644 app/securesocial/provider/DefaultUserService.java create mode 100644 app/securesocial/provider/IdentityProvider.java create mode 100644 app/securesocial/provider/OAuth1Provider.java create mode 100644 app/securesocial/provider/OAuth2Provider.java create mode 100644 app/securesocial/provider/OpenIDOAuthHybridProvider.java create mode 100644 app/securesocial/provider/OpenIDProvider.java create mode 100644 app/securesocial/provider/ProviderRegistry.java create mode 100644 app/securesocial/provider/ProviderType.java create mode 100644 app/securesocial/provider/SocialUser.java create mode 100644 app/securesocial/provider/UserId.java create mode 100644 app/securesocial/provider/UserService.java create mode 100644 app/securesocial/provider/providers/FacebookProvider.java create mode 100644 app/securesocial/provider/providers/FoursquareProvider.java create mode 100644 app/securesocial/provider/providers/GoogleProvider.java create mode 100644 app/securesocial/provider/providers/LinkedInProvider.java create mode 100644 app/securesocial/provider/providers/MyOpenIDProvider.java create mode 100644 app/securesocial/provider/providers/TwitterProvider.java create mode 100644 app/securesocial/provider/providers/UsernamePasswordProvider.java create mode 100644 app/securesocial/provider/providers/WordpressProvider.java create mode 100644 app/securesocial/provider/providers/YahooProvider.java create mode 100644 app/views/securesocial/Mails/sendActivationEmail.html create mode 100644 app/views/securesocial/SecureSocial/login.html create mode 100644 app/views/securesocial/SecureSocial/main.html create mode 100644 app/views/securesocial/SecureSocial/noticePage.html create mode 100644 app/views/securesocial/UsernamePasswordController/signup.html create mode 100644 build.xml create mode 100644 commands.py create mode 100644 commands.pyc create mode 100644 conf/dependencies.yml create mode 100644 conf/messages create mode 100644 conf/routes create mode 100644 documentation/images/loginPage.png create mode 100644 documentation/images/signUpPage.png create mode 100644 documentation/manual/home.textile create mode 100644 public/images/providers/facebook.png create mode 100644 public/images/providers/foursquare.png create mode 100644 public/images/providers/google.png create mode 100644 public/images/providers/linkedin.png create mode 100644 public/images/providers/myopenid.png create mode 100644 public/images/providers/twitter.png create mode 100644 public/images/providers/wordpress.png create mode 100644 public/images/providers/yahoo.png create mode 100644 public/stylesheets/bootstrap-1.3.0/bootstrap.min.css create mode 100644 samples-and-tests/testapp/app/controllers/Application.java create mode 100644 samples-and-tests/testapp/app/views/Application/index.html create mode 100644 samples-and-tests/testapp/app/views/errors/404.html create mode 100644 samples-and-tests/testapp/app/views/errors/500.html create mode 100644 samples-and-tests/testapp/app/views/main.html create mode 100644 samples-and-tests/testapp/conf/application.conf create mode 100644 samples-and-tests/testapp/conf/dependencies.yml create mode 100644 samples-and-tests/testapp/conf/messages create mode 100644 samples-and-tests/testapp/conf/routes create mode 100644 samples-and-tests/testapp/modules/securesocial create mode 100644 samples-and-tests/testapp/public/images/favicon.png create mode 100644 samples-and-tests/testapp/public/javascripts/jquery-1.5.2.min.js create mode 100644 samples-and-tests/testapp/public/stylesheets/bootstrap-1.3.0/bootstrap.min.css create mode 100644 samples-and-tests/testapp/public/stylesheets/main.css create mode 100644 samples-and-tests/testapp/test/Application.test.html create mode 100644 samples-and-tests/testapp/test/ApplicationTest.java create mode 100644 samples-and-tests/testapp/test/BasicTest.java create mode 100644 samples-and-tests/testapp/test/data.yml create mode 100644 src/play.plugins diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..67db85882 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/README b/README index e69de29bb..73023087f 100644 --- a/README +++ b/README @@ -0,0 +1,23 @@ +h1. SecureSocial Module for Play! Framework Applications + +SecureSocial allows you to add an authentication UI to your app that works with services based on OAuth1, OAuth2, OpenID and OpenID+OAuth hybrid protocols. + +It also provides a Username and Password mechanism for users that do not wish to use existing accounts in other networks. + +The following services are supported: + +* Twitter (OAuth1) +* Facebook (OAuth2) +* Google (OpenID + OAuth Hybrid) +* Yahoo (OpenID + OAuth Hybrid) +* LinkedIn (OAuth1) +* Foursquare (OAuth2) +* MyOpenID (OpenID) +* Wordpress (OpenID) +* Username and Password + +Check the documentation folder for more information. + +h2. Licence + +SecureSocial is distributed under "Apache 2 licence":http://www.apache.org/licenses/LICENSE-2.0.html. diff --git a/Readme-Icons.txt b/Readme-Icons.txt new file mode 100644 index 000000000..08050588e --- /dev/null +++ b/Readme-Icons.txt @@ -0,0 +1,8 @@ +The icons used in this module (except the MyOpenID icon) are part of the WPZOOM Social Networking Icon Set by WPZOOM designed by David Ferreira. + +They are licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License (http://creativecommons.org/licenses/by-nc-sa/3.0/). + +Please read more at: +http://www.iconfinder.com/browse/iconset/WPZOOM_Social_Networking_Icon_Set/#readme + + diff --git a/app/controllers/securesocial/SecureSocial.java b/app/controllers/securesocial/SecureSocial.java new file mode 100644 index 000000000..968df420a --- /dev/null +++ b/app/controllers/securesocial/SecureSocial.java @@ -0,0 +1,178 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package controllers.securesocial; + +import play.Logger; +import play.i18n.Messages; +import play.libs.OAuth; +import play.mvc.Before; +import play.mvc.Controller; +import securesocial.provider.*; + +import java.util.Collection; + +/** + * This is the main controller for the SecureSocial module. + * + */ +public class SecureSocial extends Controller { + + private static final String USER_COOKIE = "securesocial.user"; + private static final String NETWORK_COOKIE = "securesocial.network"; + private static final String ORIGINAL_URL = "originalUrl"; + private static final String GET = "GET"; + private static final String ROOT = "/"; + private static final String USER = "user"; + private static final String ERROR = "error"; + private static final String SECURESOCIAL_AUTH_ERROR = "securesocial.authError"; + + /** + * Checks if there is a user logged in and redirects to the login page if not. + */ + @Before(unless={"login", "authenticate", "logout"}) + static void checkAccess() throws Throwable + { + final UserId userId = getUserId(); + + if ( userId == null ) { + final String originalUrl = request.method.equals(GET) ? request.url : ROOT; + flash.put(ORIGINAL_URL, originalUrl); + login(); + } else { + SocialUser user = UserService.find(userId); + if ( user == null ) { + // the user had the cookies but the UserService can't find it ... + // it must have been erased, redirect to login again. + clearUserId(); + login(); + } + + // if the user is using OAUTH1 or OPENID HYBRID OAUTH set the ServiceInfo + // so the app using this module can access it easily to invoke the APIs. + if ( user.authMethod == AuthenticationMethod.OAUTH1 || user.authMethod == AuthenticationMethod.OPENID_OAUTH_HYBRID ) { + final OAuth.ServiceInfo sinfo; + IdentityProvider provider = ProviderRegistry.get(user.id.provider); + if ( user.authMethod == AuthenticationMethod.OAUTH1 ) { + sinfo = ((OAuth1Provider)provider).getServiceInfo(); + } else { + sinfo = ((OpenIDOAuthHybridProvider)provider).getServiceInfo(); + } + user.serviceInfo = sinfo; + } + // make the user available in templates + renderArgs.put(USER, user); + } + } + + /** + * Returns the current user. + * + * @return SocialUser the current user + */ + public static SocialUser getCurrentUser() { + return (SocialUser) renderArgs.get(USER); + } + + /* + * Removes the SecureSocial cookies from the session. + */ + private static void clearUserId() { + session.remove(USER_COOKIE); + session.remove(NETWORK_COOKIE); + } + + /* + * Sets the SecureSocial cookies in the session. + */ + private static void setUserId(SocialUser user) { + session.put(USER_COOKIE, user.id.id); + session.put(NETWORK_COOKIE, user.id.provider.toString()); + } + + /* + * Creates a UserId object from the values stored in the session. + * + * @see UserId + * @returns UserId the user id + */ + private static UserId getUserId() { + final String userId = session.get(USER_COOKIE); + final String networkId = session.get(NETWORK_COOKIE); + + UserId id = null; + + if ( userId != null && networkId != null ) { + id = new UserId(); + id.id = userId; + id.provider = ProviderType.valueOf(networkId); + } + return id; + } + + /** + * The action for the login page. + */ + public static void login() { + final Collection providers = ProviderRegistry.all(); + flash.keep(ORIGINAL_URL); + boolean userPassEnabled = ProviderRegistry.get(ProviderType.userpass) != null; + render(providers, userPassEnabled); + + } + + /** + * The logout action. + */ + public static void logout() { + clearUserId(); + login(); + } + + /** + * This is the entry point for all authentication requests from the login page. + * The type is used to invoke the right provider. + * + * @param type The provider type as selected by the user in the login page + * @see ProviderType + * @see IdentityProvider + */ + public static void authenticate(ProviderType type) { + if ( type == null ) { + Logger.error("Provider type was missing in request"); + // just throw a 404 error + notFound(); + } + flash.keep(ORIGINAL_URL); + + IdentityProvider provider = ProviderRegistry.get(type); + String originalUrl = null; + + try { + SocialUser user = provider.authenticate(); + setUserId(user); + originalUrl = flash.get(ORIGINAL_URL); + } catch ( Exception e ) { + e.printStackTrace(); + Logger.error(e, "Error authenticating user"); + if ( flash.get(ERROR) == null ) { + flash.error(Messages.get(SECURESOCIAL_AUTH_ERROR)); + } + flash.keep(ORIGINAL_URL); + login(); + } + redirect( originalUrl != null ? originalUrl : ROOT); + } +} diff --git a/app/controllers/securesocial/UsernamePasswordController.java b/app/controllers/securesocial/UsernamePasswordController.java new file mode 100644 index 000000000..f2a22f660 --- /dev/null +++ b/app/controllers/securesocial/UsernamePasswordController.java @@ -0,0 +1,134 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ +package controllers.securesocial; + +import notifiers.securesocial.Mails; +import play.Logger; +import play.data.validation.Email; +import play.data.validation.Equals; +import play.data.validation.Required; +import play.i18n.Messages; +import play.libs.Crypto; +import play.mvc.Controller; +import play.mvc.Router; +import securesocial.provider.*; + +/** + * The controller for the UI required by the Username Password Provider. + */ +public class UsernamePasswordController extends Controller +{ + private static final String USER_NAME = "userName"; + private static final String SECURESOCIAL_USER_NAME_TAKEN = "securesocial.userNameTaken"; + private static final String SECURESOCIAL_ERROR_CREATING_ACCOUNT = "securesocial.errorCreatingAccount"; + private static final String SECURESOCIAL_ACCOUNT_CREATED = "securesocial.accountCreated"; + private static final String SECURESOCIAL_ACTIVATION_TITLE = "securesocial.activationTitle"; + private static final String SECURESOCIAL_SECURE_SOCIAL_NOTICE_PAGE_HTML = "securesocial/SecureSocial/noticePage.html"; + private static final String DISPLAY_NAME = "displayName"; + private static final String EMAIL = "email"; + private static final String SECURESOCIAL_INVALID_LINK = "securesocial.invalidLink"; + private static final String SECURESOCIAL_ACTIVATION_SUCCESS = "securesocial.activationSuccess"; + private static final String SECURESOCIAL_SECURE_SOCIAL_LOGIN = "securesocial.SecureSocial.login"; + private static final String SECURESOCIAL_ACTIVATE_TITLE = "securesocial.activateTitle"; + + /** + * Renders the sign up page. + */ + public static void signup() { + render(); + } + + /** + * Creates an account + * + * @param userName The username + * @param displayName The user's full name + * @param email The email + * @param password The password + * @param password2 The password verification + */ + public static void createAccount(@Required(message = "securesocial.required") String userName, + @Required String displayName, + @Required @Email(message = "securesocial.invalidEmail") String email, + @Required String password, + @Required @Equals(message = "securesocial.passwordsMustMatch", value = "password") String password2) { + if ( validation.hasErrors() ) { + tryAgain(userName, displayName, email); + } + + UserId id = new UserId(); + id.id = userName; + id.provider = ProviderType.userpass; + + if ( UserService.find(id) != null ) { + validation.addError(USER_NAME, Messages.get(SECURESOCIAL_USER_NAME_TAKEN)); + tryAgain(userName, displayName, email); + } + SocialUser user = new SocialUser(); + user.id = id; + user.displayName = displayName; + user.email = email; + user.password = Crypto.passwordHash(password); + // the user will remain inactive until the email verification is done. + user.isEmailVerified = false; + user.authMethod = AuthenticationMethod.USER_PASSWORD; + + try { + UserService.save(user); + } catch ( Throwable e ) { + Logger.error(e, "Error while invoking UserService.save()"); + flash.error(Messages.get(SECURESOCIAL_ERROR_CREATING_ACCOUNT)); + tryAgain(userName, displayName, email); + } + + // create an activation id + final String uuid = UserService.createActivation(user); + Mails.sendActivationEmail(user, uuid); + flash.success(Messages.get(SECURESOCIAL_ACCOUNT_CREATED)); + final String title = Messages.get(SECURESOCIAL_ACTIVATION_TITLE, user.displayName); + render(SECURESOCIAL_SECURE_SOCIAL_NOTICE_PAGE_HTML, title); + } + + private static void tryAgain(String username, String displayName, String email) { + flash.put(USER_NAME, username); + flash.put(DISPLAY_NAME, displayName); + flash.put(EMAIL, email); + validation.keep(); + signup(); + } + + /** + * The action invoked from the activation email the user receives after signing up. + * + * @param uuid The activation id + */ + public static void activate(String uuid) { + try { + if ( UserService.activate(uuid) == false ) { + flash.error( Messages.get(SECURESOCIAL_INVALID_LINK) ); + } else { + flash.success(Messages.get(SECURESOCIAL_ACTIVATION_SUCCESS, Router.reverse(SECURESOCIAL_SECURE_SOCIAL_LOGIN))); + } + } catch ( Throwable t) { + Logger.error(t, "Error while activating account"); + flash.error(Messages.get(SECURESOCIAL_ERROR_CREATING_ACCOUNT)); + } + final String title = Messages.get(SECURESOCIAL_ACTIVATE_TITLE); + render(SECURESOCIAL_SECURE_SOCIAL_NOTICE_PAGE_HTML, title); + } +} diff --git a/app/notifiers/securesocial/Mails.java b/app/notifiers/securesocial/Mails.java new file mode 100644 index 000000000..8d094b896 --- /dev/null +++ b/app/notifiers/securesocial/Mails.java @@ -0,0 +1,50 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ +package notifiers.securesocial; + +import play.Play; +import play.mvc.Mailer; +import play.mvc.Router; +import securesocial.provider.SocialUser; + +import java.util.HashMap; +import java.util.Map; + +/** + * A helper class to send welcome emails to users that signed up using the + * Username Password controller + * + * @see securesocial.provider.providers.UsernamePasswordProvider + * @see controllers.securesocial.UsernamePasswordController + */ +public class Mails extends Mailer { + private static final String SECURESOCIAL_MAILER_SUBJECT = "securesocial.mailer.subject"; + private static final String SECURESOCIAL_MAILER_FROM = "securesocial.mailer.from"; + private static final String SECURESOCIAL_USERNAME_PASSWORD_CONTROLLER_ACTIVATE = "securesocial.UsernamePasswordController.activate"; + private static final String UUID = "uuid"; + + public static void sendActivationEmail(SocialUser user, String uuid) { + setSubject( Play.configuration.getProperty(SECURESOCIAL_MAILER_SUBJECT)); + setFrom(Play.configuration.getProperty(SECURESOCIAL_MAILER_FROM)); + addRecipient(user.email); + Map args = new HashMap(); + args.put(UUID, uuid); + String activationUrl = Router.getFullUrl(SECURESOCIAL_USERNAME_PASSWORD_CONTROLLER_ACTIVATE, args); + send(user, activationUrl); + } +} diff --git a/app/securesocial/jobs/Bootstrap.java b/app/securesocial/jobs/Bootstrap.java new file mode 100644 index 000000000..ac11a4c7d --- /dev/null +++ b/app/securesocial/jobs/Bootstrap.java @@ -0,0 +1,93 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.jobs; + +import play.Logger; +import play.Play; +import play.jobs.Job; +import play.jobs.OnApplicationStart; +import securesocial.provider.*; + +import java.lang.reflect.Modifier; +import java.util.*; + +/** + * A Job to load the providers and set the user service. + */ +@OnApplicationStart +public class Bootstrap extends Job { + private static final String SECURESOCIAL_PROVIDERS = "securesocial.providers"; + private static final String SEPARATOR = ","; + private static final String SECURESOCIAL = "securesocial"; + + @Override + public void doJob() throws Exception { + // register providers + final List providers = Play.classloader.getAssignableClasses(IdentityProvider.class); + if ( providers.size() > 0 ) { + Map availableProviders = new LinkedHashMap(); + for( Class clazz : providers ) { + if ( !Modifier.isAbstract(clazz.getModifiers()) ) { + IdentityProvider provider = (IdentityProvider) clazz.newInstance(); + availableProviders.put(provider.type, provider); + } + } + // register them in the preferred order + final String s = Play.configuration.getProperty(SECURESOCIAL_PROVIDERS); + + if ( s != null && s.length() > 0) { + final String[] requestedProviders = s.split(SEPARATOR); + for ( String type: requestedProviders ) { + try { + ProviderRegistry.register(availableProviders.get(ProviderType.valueOf(type))); + } catch ( IllegalArgumentException e ) { + Logger.error("Unknown type specified in securesocial.providers: %s", type); + } + } + } else { + for( IdentityProvider p : availableProviders.values()) { + ProviderRegistry.register(p); + } + } + } else { + Logger.fatal("Unable to find identity providers."); + } + + // set the user service + final List classes = Play.classloader.getAssignableClasses(UserService.Service.class); + UserService.Service service = null; + + int classesFound = classes.size(); + if ( classesFound == 1 ) { + // use the default implementation + Logger.info("Using default user service"); + service = new DefaultUserService(); + } else if ( classesFound == 2 ) { + // a custom implementation was found. use it instead of the default + Class clazz = classes.get(0); + if ( clazz.getName().startsWith(SECURESOCIAL) ) { + clazz = classes.get(1); + } + service = (UserService.Service) clazz.newInstance(); + Logger.info("Using custom user service: %s", service.getClass()); + } else { + // should not happen unless someone implements the interface more than once. + Logger.fatal("More than one custom UserService was found. Unable to initialize."); + } + UserService.setService(service); + } +} diff --git a/app/securesocial/jobs/PendingActivationsJobs.java b/app/securesocial/jobs/PendingActivationsJobs.java new file mode 100644 index 000000000..4cc432144 --- /dev/null +++ b/app/securesocial/jobs/PendingActivationsJobs.java @@ -0,0 +1,35 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ +package securesocial.jobs; + +import play.jobs.Every; +import play.jobs.Job; +import securesocial.provider.UserService; + +/** + * A Job that deletes pending activations every 24 hours + * + */ +@Every("24h") +public class PendingActivationsJobs extends Job +{ + @Override + public void doJob() throws Exception { + UserService.deletePendingActivations(); + } +} diff --git a/app/securesocial/provider/AccessDeniedException.java b/app/securesocial/provider/AccessDeniedException.java new file mode 100644 index 000000000..8171d6f29 --- /dev/null +++ b/app/securesocial/provider/AccessDeniedException.java @@ -0,0 +1,29 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import play.utils.FastRuntimeException; + +/** + * An exception thrown when the user denies access to the application + * in the login page of the 3rd party service + */ +public class AccessDeniedException extends FastRuntimeException { + public AccessDeniedException() { + super(); + } +} diff --git a/app/securesocial/provider/AuthenticationException.java b/app/securesocial/provider/AuthenticationException.java new file mode 100644 index 000000000..59baf8e26 --- /dev/null +++ b/app/securesocial/provider/AuthenticationException.java @@ -0,0 +1,39 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import play.libs.OAuth; +import play.utils.FastRuntimeException; + +/** + * An exception thrown when there is an error in the authentication flow + */ +public class AuthenticationException extends FastRuntimeException { + private OAuth.Error error; + + public AuthenticationException() { + super(); + } + + public AuthenticationException(OAuth.Error error) { + super(error.details()); + } + + public OAuth.Error getError() { + return error; + } +} diff --git a/app/securesocial/provider/AuthenticationMethod.java b/app/securesocial/provider/AuthenticationMethod.java new file mode 100644 index 000000000..cef3520ca --- /dev/null +++ b/app/securesocial/provider/AuthenticationMethod.java @@ -0,0 +1,28 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +/** + * Authentication methods used by the Identity Providers + */ +public enum AuthenticationMethod { + OAUTH1, + OAUTH2, + OPENID, + OPENID_OAUTH_HYBRID, + USER_PASSWORD +} diff --git a/app/securesocial/provider/DefaultUserService.java b/app/securesocial/provider/DefaultUserService.java new file mode 100644 index 000000000..f58c8f7af --- /dev/null +++ b/app/securesocial/provider/DefaultUserService.java @@ -0,0 +1,70 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import play.libs.Codec; + +import java.util.HashMap; +import java.util.Map; + +/** + * The default user service provided with SecureSocial. + * If users need to find/save users in a custom backing store they only + * need to provide an implementation of the UserService.Service interface in their app. It will be picked up automatically. + * + * This class it not suitable for a production environment. It is only meant to be used in development. For production use + * you need to provide your own implementation. + * + * @see UserService.Service + * @see securesocial.jobs.Bootstrap + */ +public class DefaultUserService implements UserService.Service { + + private Map users = new HashMap(); + private Map activations = new HashMap(); + + public SocialUser find(UserId id) { + return users.get(id.id + id.provider.toString()); + } + + public void save(SocialUser user) { + users.put(user.id.id + user.id.provider.toString(), user); + } + + public String createActivation(SocialUser user) { + final String uuid = Codec.UUID(); + activations.put(uuid, user); + return uuid; + } + + public boolean activate(String uuid) { + SocialUser user = activations.get(uuid); + boolean result = false; + + if( user != null ) { + user.isEmailVerified = true; + save(user); + activations.remove(uuid); + result = true; + } + return result; + } + + public void deletePendingActivations() { + activations.clear(); + } +} diff --git a/app/securesocial/provider/IdentityProvider.java b/app/securesocial/provider/IdentityProvider.java new file mode 100644 index 000000000..e5f5c424e --- /dev/null +++ b/app/securesocial/provider/IdentityProvider.java @@ -0,0 +1,129 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Base class for all itendity providers + */ +public abstract class IdentityProvider { + /** + * The provider ID. + */ + public ProviderType type; + + /** + * The authentication method used by this provider + */ + public AuthenticationMethod authMethod; + + private static final String TYPE = "type"; + private static final String SECURESOCIAL_SECURE_SOCIAL_AUTHENTICATE = "securesocial.SecureSocial.authenticate"; + private static final String SECURESOCIAL = "securesocial."; + private static final String DOT = "."; + + /** + * Creates a new IdentityProvider + * + * @param type The type for this provider + * @param authMethod The authentication method used by this provider + */ + protected IdentityProvider(ProviderType type, AuthenticationMethod authMethod) { + this.type = type; + this.authMethod = authMethod; + } + + @Override + public String toString() { + return type.toString(); + } + + /** + * The authentication flow starts here. This method is called from the + * SecureSocial controller + * + * @return A SocialUser if the user was authenticated properly + */ + public SocialUser authenticate() { + // authenticate against the 3rd party service (facebook, twitter, etc) + Map authContext = new HashMap(); + SocialUser user = doAuth(authContext); + + // if user authenticated correctly, retrieve some profile information + fillProfile(user, authContext); + + // save the user + user.lastAccess = new Date(); + UserService.save(user); + + // we're done. + return user; + } + + /** + * A helper method to create a user with some authentication details. + * + * @return A SocialUser object + */ + protected SocialUser createUser() { + SocialUser user = new SocialUser(); + user.id = new UserId(); + user.id.provider = type; + user.authMethod = authMethod; + return user; + } + + /** + * Returns the full url for the authenticate action. + * + * @return A url + */ + public String getFullUrl() { + Map args = new HashMap(); + args.put(TYPE, type); + return play.mvc.Router.getFullUrl(SECURESOCIAL_SECURE_SOCIAL_AUTHENTICATE, args); + } + + /** + * Subclasses must implement the authentication logic in this method + * + * @param authContext This map can be used to store information that fillProfile will need to complete the operation + * @return SocialUser the authenticated user + */ + protected abstract SocialUser doAuth(Map authContext); + + /** + * Once the user is authenticated this method is called to retrieve profile information from the provider. + * + * @param user A SocialUser + * @param authContext This map can contain information collected during the doAuth call. + */ + protected abstract void fillProfile(SocialUser user, Map authContext); + + /** + * A helper method to return the keys for the properties required by the provider. + * + * @param type The provider type + * @return A String + */ + public static String getPropertiesKey(ProviderType type) { + return new StringBuilder(SECURESOCIAL).append(type).append(DOT).toString(); + } +} diff --git a/app/securesocial/provider/OAuth1Provider.java b/app/securesocial/provider/OAuth1Provider.java new file mode 100644 index 000000000..68ec43e21 --- /dev/null +++ b/app/securesocial/provider/OAuth1Provider.java @@ -0,0 +1,116 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import play.Play; +import play.cache.Cache; +import play.libs.OAuth; +import play.mvc.Scope; +import play.mvc.results.Redirect; + +import java.util.Map; + +/** + * A provider that handles the OAuth1 authentication flow + */ +public abstract class OAuth1Provider extends IdentityProvider { + + private static final String DENIED = "denied"; + private static final String SECURESOCIAL = "securesocial."; + private OAuth.ServiceInfo serviceInfo; + private static final String REQUEST_TOKEN_URL = "requestTokenURL"; + private static final String ACCESS_TOKEN_URL = "accessTokenURL"; + private static final String AUTHORIZATION_URL = "authorizationURL"; + private static final String CONSUMER_KEY = "consumerKey"; + private static final String CONSUMER_SECRET = "consumerSecret"; + + + protected OAuth1Provider(ProviderType type) + { + super(type, AuthenticationMethod.OAUTH1); + serviceInfo = createServiceInfo(getPropertiesKey(type)); + } + + /** + * Gets the service info required to invoke the APIs for this provider. + * + * @return A play.libs.OAuth.ServiceInfo object + */ + public OAuth.ServiceInfo getServiceInfo() { + return serviceInfo; + } + + /** + * Creates a ServiceInfo using the properties in the application.conf + * + * @param key The key for this provider + * @return A OAuth.ServiceInfo object + */ + public static OAuth.ServiceInfo createServiceInfo(String key) { + return new OAuth.ServiceInfo( + Play.configuration.getProperty(key + REQUEST_TOKEN_URL), + Play.configuration.getProperty(key + ACCESS_TOKEN_URL), + Play.configuration.getProperty(key + AUTHORIZATION_URL), + Play.configuration.getProperty(key + CONSUMER_KEY), + Play.configuration.getProperty(key + CONSUMER_SECRET) + ); + + } + + @Override + public SocialUser doAuth(Map authContext) throws AccessDeniedException { + Scope.Params params = Scope.Params.current(); + + if ( params.get(DENIED) != null ) { + throw new AccessDeniedException(); + } + + final String key = new StringBuilder(SECURESOCIAL).append(Scope.Session.current().getId()).toString(); + OAuth service = OAuth.service(serviceInfo); + + if ( !OAuth.isVerifierResponse() ) { + // first step on the authentication process + OAuth.Response response = service.retrieveRequestToken(); + if ( response.error != null ) { + // there was an error retrieving the access token + throw new AuthenticationException(response.error); + } + SocialUser user = createUser(); + user.token = response.token; + user.secret = response.secret; + Cache.add(key, user); + throw new Redirect( service.redirectUrl(response.token), false); + } + + // the OAuth provider is redirecting back to us + SocialUser user = (SocialUser) Cache.get(key); + if ( user == null ) { + throw new AuthenticationException(); + } + Cache.delete(key); + OAuth.Response response = service.retrieveAccessToken(user.token, user.secret); + if ( response.error != null ) { + throw new AuthenticationException(response.error); + } + + // all was ok, replace the tokens + user.token = response.token; + user.secret = response.secret; + user.serviceInfo = serviceInfo; + return user; + } +} diff --git a/app/securesocial/provider/OAuth2Provider.java b/app/securesocial/provider/OAuth2Provider.java new file mode 100644 index 000000000..c7981e3b3 --- /dev/null +++ b/app/securesocial/provider/OAuth2Provider.java @@ -0,0 +1,155 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import play.Play; +import play.libs.OAuth2; +import play.mvc.Scope; +import play.mvc.results.Redirect; + +import java.util.Map; + +/** + * A provider that handles the OAuth2 authentication flow + */ +public abstract class OAuth2Provider extends IdentityProvider +{ + private OAuth2 service; + private String[] scope; + private static final String SCOPE = "scope"; + private static final String AUTHORIZATION_URL = "authorizationURL"; + private static final String ACCESS_TOKEN_URL = "accessTokenURL"; + private static final String CLIENTID = "clientid"; + private static final String SECRET = "secret"; + private static final String QUESTION_MARK = "?"; + private static final String AMPERSAND = "&"; + private static final String CLIENT_ID = "client_id="; + private static final String EQUALS_SIGN = "="; + private static final String REDIRECT_URI = "&redirect_uri="; + private static final String ACCESS_TOKEN = "access_token"; + private static final String OAUTH_TOKEN = "oauth_token"; + private static final String ERROR = "error"; + + protected OAuth2Provider(ProviderType type) { + super(type, AuthenticationMethod.OAUTH2); + final String key = getPropertiesKey(type); + service = createOAuth2(key); + scope = getScope(key, SCOPE); + } + + /** + * Creates an OAuth2 object using the properties specified for the provider. + * + * @param key The provider key + * @return An OAuth2 object + */ + private OAuth2 createOAuth2(String key) { + return new OAuth2( + Play.configuration.getProperty(key + AUTHORIZATION_URL), + Play.configuration.getProperty(key + ACCESS_TOKEN_URL), + Play.configuration.getProperty(key + CLIENTID), + Play.configuration.getProperty(key + SECRET) + ); + } + + /** + * Returns the scope specified for this provider in the properties file (eg: facebook.scope) + * + * @param providerKey The properties key + * @param scopeKey The property name + * @return The scope + */ + public static String[] getScope(String providerKey, String scopeKey) { + final String s = Play.configuration.getProperty(providerKey + SCOPE); + String []scope = null; + if ( s != null && s.trim().length() > 0) { + scope = new String[] {scopeKey, s}; + } + return scope; + } + + /** + * @see IdentityProvider#doAuth(java.util.Map) + * @return + */ + @Override + protected SocialUser doAuth(Map authContext) { + Scope.Params params = Scope.Params.current(); + + if ( params.get(ERROR) != null ) { + // todo: improve this. Get details of the error and include them in the exception. + throw new AuthenticationException(); + } + + if ( !OAuth2.isCodeResponse() ) { + StringBuilder authUrl = new StringBuilder(service.authorizationURL); + String delimiter = service.authorizationURL.indexOf(QUESTION_MARK) == -1 ? QUESTION_MARK : AMPERSAND; + authUrl.append(delimiter).append(CLIENT_ID).append(service.clientid); + if ( scope != null ) { + authUrl.append(AMPERSAND).append(scope[0]).append(EQUALS_SIGN).append(scope[1]); + } + authUrl.append(REDIRECT_URI).append(getFullUrl()); + throw new Redirect(authUrl.toString()); + } + + final String authUrl = getFullUrl(); + OAuth2.Response response = service.retrieveAccessToken(authUrl); + if ( response == null ) { + throw new AuthenticationException(); + } + + String accessTokenFromJson = null; + if ( response.error != null ) { + if ( response.error.type == OAuth2.Error.Type.UNKNOWN ) { + // the OAuth2 class is expecting the access token in the query string. + // this is not what the OAuth2 spec says. Facebook works, but Foursquare fails for example. + // So I'm going to check if the token is there before throwing the exception. + // todo: fix the OAuth2 class. + JsonElement asJson = response.httpResponse.getJson(); + + if ( asJson != null ) { + JsonObject body = asJson.getAsJsonObject(); + if ( body != null ) { + // this is what many libraries expect (probably because Facebook returns it) + JsonElement token = body.get(ACCESS_TOKEN); + if ( token != null ) { + accessTokenFromJson = token.getAsString(); + } else { + // this is what should be returned as defined in the OAuth2 spec + token = body.get(OAUTH_TOKEN); + if ( token != null ) { + accessTokenFromJson = token.getAsString(); + } + } + } + } + } + + // if the workaround did not find the token then we really have an error, + // so I need to throw the exception + if ( accessTokenFromJson == null ) { + // todo: add error to the exception + throw new AuthenticationException(); + } + } + SocialUser user = createUser(); + user.accessToken = accessTokenFromJson == null ? response.accessToken : accessTokenFromJson; + return user; + } +} diff --git a/app/securesocial/provider/OpenIDOAuthHybridProvider.java b/app/securesocial/provider/OpenIDOAuthHybridProvider.java new file mode 100644 index 000000000..ae43e15ce --- /dev/null +++ b/app/securesocial/provider/OpenIDOAuthHybridProvider.java @@ -0,0 +1,136 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package securesocial.provider; + +import play.Logger; +import play.Play; +import play.libs.OAuth; +import play.libs.OpenID; +import play.libs.WS; +import play.mvc.Scope; +import play.mvc.results.Redirect; + +import java.util.Map; + +/** + * Base class for all providers that need to support the OpenID + OAuth Hybrid protocol + */ +public abstract class OpenIDOAuthHybridProvider extends OpenIDProvider +{ + private OAuth.ServiceInfo sinfo; + private String[][] oauthParameters; + private static final String OPENID_EXT2_SCOPE = "openid.ext2.scope"; + private static final String OPENID_NS_EXT2 = "openid.ns.ext2"; + private static final String HTTP_SPECS_OPENID_NET_EXTENSIONS_OAUTH_1_0 = "http://specs.openid.net/extensions/oauth/1.0"; + private static final String OPENID_EXT2_CONSUMER = "openid.ext2.consumer"; + private static final String OPENID_OAUTH_REQUEST_TOKEN = "openid.oauth.request_token"; + private static final String OPENID_EXT2_REQUEST_TOKEN = "openid.ext2.request_token"; + private static final String AMPERSAND = "&"; + private static final String EQUALS = "="; + private static final String EMPTY_SECRET = ""; + + protected OpenIDOAuthHybridProvider(ProviderType type, String userFormat) { + super(type, userFormat); + final String key = getPropertiesKey(type); + sinfo = OAuth1Provider.createServiceInfo(key); + + final String scope[] = OAuth2Provider.getScope(key, OPENID_EXT2_SCOPE); + if ( scope != null ) { + oauthParameters = new String[3][2]; + oauthParameters[2][0] = scope[0]; + oauthParameters[2][1] = scope[1]; + + } else { + oauthParameters = new String[2][2]; + } + oauthParameters[0] = new String[] {OPENID_NS_EXT2, HTTP_SPECS_OPENID_NET_EXTENSIONS_OAUTH_1_0}; + oauthParameters[1] = new String[] {OPENID_EXT2_CONSUMER, sinfo.consumerKey}; + + } + + /** + * Returns the ServiceInfo needed to invoke APIs in the service this provider represents + * + * + * @return A OAuth.ServiceInfo object + */ + public OAuth.ServiceInfo getServiceInfo() { + return sinfo; + } + + /** + * Executes the OpenID + OAuth1 hybrid flow. + * + * @see OpenIDProvider#doAuth(java.util.Map) + * @see IdentityProvider#doAuth(java.util.Map) + */ + protected SocialUser doAuth(Map authContext) { + SocialUser user; + + try { + user = super.doAuth(authContext); + } catch ( Redirect redirect ) { + // todo: add this behaviour into Play's OpenID class. + redirect.url = addParameters(new StringBuilder(redirect.url), oauthParameters).toString(); + throw redirect; + } + + // OpenID flow is done, complete the OAuth part + Scope.Params params = Scope.Params.current(); + String token = params.get(OPENID_OAUTH_REQUEST_TOKEN); + + if ( token == null ) { + token = params.get(OPENID_EXT2_REQUEST_TOKEN); + } + if ( token == null ) { + Logger.error("Request token is missing in OpenID+OAuth callback. Provider: " + type); + throw new AuthenticationException(); + } + if ( Logger.isDebugEnabled() ) { + Logger.debug("openid.ext2.scope = " + params.get(OPENID_EXT2_SCOPE)); + Logger.debug("openid.ext2.request_token = " + token); + } + OAuth oauth = OAuth.service(sinfo); + OAuth.Response response = oauth.retrieveAccessToken(token, EMPTY_SECRET); + if ( response.error != null ) { + Logger.error("Error retrieving access token from %s, : %s", type, response.error.toString()); + throw new AuthenticationException(response.error); + } + + user.token = response.token; + user.secret = response.secret; + user.serviceInfo = sinfo; + + if ( Logger.isDebugEnabled() ) { + Logger.debug("After OAuth exchange: request token = " + token + " -> token = " + user.token + " - secret = " + user.secret); + } + user.authMethod = AuthenticationMethod.OPENID_OAUTH_HYBRID; + return user; + } + + // a helper method to add parameters to some urls ... + // todo: fix play so this is not needed. + private StringBuilder addParameters(StringBuilder url, String [][]additionalParams) { + // this is a hack, retrieveVerificationCode should allow passing parameters + if ( additionalParams != null ) { + for ( int i = 0 ; i < additionalParams.length ; i++ ) { + url.append(AMPERSAND).append(additionalParams[i][0]).append(EQUALS).append( WS.encode(additionalParams[i][1])); + } + } + return url; + } +} diff --git a/app/securesocial/provider/OpenIDProvider.java b/app/securesocial/provider/OpenIDProvider.java new file mode 100644 index 000000000..295a486b6 --- /dev/null +++ b/app/securesocial/provider/OpenIDProvider.java @@ -0,0 +1,119 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import play.i18n.Messages; +import play.libs.OpenID; +import play.mvc.Http; +import play.mvc.Scope; + +import java.util.Map; + +/** + * An OpenID provider + */ +public abstract class OpenIDProvider extends IdentityProvider +{ + private boolean needsUsername; + private String userFormat; + private static final String USERNAME_TAG = "{username}"; + private static final String OPENID_USER = "openid.user"; + private static final String USERNAME_REGEX = "\\{username\\}"; + protected static final String USER_INFO = "userInfo"; + private static final String SECURESOCIAL_OPEN_ID_USER_NOT_SPECIFIED = "securesocial.openIdUserNotSpecified"; + + /** + * Creates an OpenID provider. + * + * @param type the Provider Type (eg: myopenid) + * @param userFormat The user url format (eg: http://{username}.wordpress.com) + */ + protected OpenIDProvider(ProviderType type, String userFormat) { + super(type, AuthenticationMethod.OPENID); + this.userFormat = userFormat; + needsUsername = userFormat.indexOf(USERNAME_TAG) != -1; + } + + /** + * Returns the user url (eg: http://user.wordpress.com) for an OpenID service. + * The method checks if the userFormat has a {username} tag and if it does then + * looks for the username value in the request parameters. + * + * If there is no {username} tag the userFormat is used a passed by the subclass. + * This is because some providers (eg: google or yahoo) do not need the username in the + * url. + * + * @return The url representing the user. + */ + protected String getUser() { + final String user; + if ( needsUsername ) { + final String username = Scope.Params.current().get(OPENID_USER); + if ( username == null || username.trim().length() == 0) { + Scope.Flash.current().error(Messages.get(SECURESOCIAL_OPEN_ID_USER_NOT_SPECIFIED)); + throw new AuthenticationException(); + } + user = userFormat.replaceFirst(USERNAME_REGEX, username); + } else { + user = userFormat; + } + return user; + } + + + /** + * Returns true if the userFormat does not have a {username} tag. + * @return A boolean + */ + protected boolean needsUsername() { + return needsUsername; + } + + /** + * @see IdentityProvider#doAuth(java.util.Map) + */ + @Override + protected SocialUser doAuth(Map authContext) { + if ( !OpenID.isAuthenticationResponse() ) { + OpenID openId = OpenID.id(getUser()); + final String url = getFullUrl(); + openId.returnTo( url ); + openId.forRealm( Http.Request.current().getBase() ); + configure(openId); + if ( !openId.verify() ) { + throw new AuthenticationException(); + } + } + // + OpenID.UserInfo verifiedUser = OpenID.getVerifiedID(); + if ( verifiedUser == null ) { + throw new AuthenticationException(); + } + authContext.put(USER_INFO, verifiedUser); + SocialUser user = createUser(); + user.id.id = verifiedUser.id; + return user; + } + + /** + * This method allows subclasses to set up additional settings (such as specifying attribute exchange or sreg) + * before redirecting the user to the OpenId provider. + * + * @param openId + */ + protected abstract void configure(OpenID openId); +} diff --git a/app/securesocial/provider/ProviderRegistry.java b/app/securesocial/provider/ProviderRegistry.java new file mode 100644 index 000000000..2b643ec6b --- /dev/null +++ b/app/securesocial/provider/ProviderRegistry.java @@ -0,0 +1,69 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import play.Logger; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A Provider Registry. All the providers discovered by the bootstrap job are registered here. + * The SecureSocial controller looks for the providers here as well. + * + * @see securesocial.jobs.Bootstrap + * @see controllers.securesocial.SecureSocial + */ +public class ProviderRegistry { + private static Map providers = new LinkedHashMap(); + + /** + * Registgers a provider. + * + * @param p The identity provider. + * @throws RuntimeException if there is another provider registered with the same type. + * @see ProviderType + */ + public static void register(IdentityProvider p) { + if ( providers.get(p.type) != null ) { + // make sure the same type is not used more than once + Logger.error("Tried to register provider for type: " + p.type + " but it is registered already. Providers: " + providers); + throw new RuntimeException("There is already a provider registered for type: " + p.type); + } + providers.put(p.type, p); + Logger.info("Registered Identity Provider: " + p.type); + } + + /** + * Returns a provider that matches the specified type. + * + * @param type A ProviderType + * @return An IdentityProvider + */ + public static IdentityProvider get(ProviderType type) { + return providers.get(type); + } + + /** + * Returns all the registered providers. + * @return A Collection with the providers + */ + public static Collection all() { + return providers.values(); + } +} diff --git a/app/securesocial/provider/ProviderType.java b/app/securesocial/provider/ProviderType.java new file mode 100644 index 000000000..7b6130cdd --- /dev/null +++ b/app/securesocial/provider/ProviderType.java @@ -0,0 +1,32 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +/** + * An enum with the authentication providers supported by SecureSocial + */ +public enum ProviderType { + twitter, + facebook, + google, + yahoo, + linkedin, + foursquare, + userpass, + wordpress, + myopenid +} diff --git a/app/securesocial/provider/SocialUser.java b/app/securesocial/provider/SocialUser.java new file mode 100644 index 000000000..2c821d54f --- /dev/null +++ b/app/securesocial/provider/SocialUser.java @@ -0,0 +1,90 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +import play.libs.OAuth; + +import java.util.Date; + +/** + * A class representing a conected user and its authentication details. + */ +public class SocialUser implements java.io.Serializable { + /** + * The user id + */ + public UserId id; + + /** + * The user full name. + */ + public String displayName; + + /** + * The user's email + */ + public String email; + + /** + * A URL pointing to an avatar + */ + public String avatarUrl; + + /** + * The time of the last login. This is set by the SecureSocial controller. + */ + public Date lastAccess; + + /** + * The method that was used to authenticate the user. + */ + public AuthenticationMethod authMethod; + + /** + * The service info required to make calls to the API for OAUTH1 users + * (available when authMethod is OAUTH1 or OPENID_OAUTH_HYBRID) + * + * Note: this value does not need to be persisted by UserService since it is set automatically + * in the SecureSocial Controller for each request that needs it. + */ + public OAuth.ServiceInfo serviceInfo; + + /** + * The OAuth1 token (available when authMethod is OAUTH1 or OPENID_OAUTH_HYBRID) + */ + public String token; + + /** + * The OAuth1 secret (available when authMethod is OAUTH1 or OPENID_OAUTH_HYBRID) + */ + public String secret; + + /** + * The OAuth2 access token (available when authMethod is OAUTH2) + */ + public String accessToken; + + /** + * The user password (available when authMethod is USER_PASSWORD) + */ + public String password; + + /** + * A boolean indicating if the user has validated his email adddress (available when authMethod is USER_PASSWORD) + */ + public boolean isEmailVerified; +} diff --git a/app/securesocial/provider/UserId.java b/app/securesocial/provider/UserId.java new file mode 100644 index 000000000..a057a1b11 --- /dev/null +++ b/app/securesocial/provider/UserId.java @@ -0,0 +1,33 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +/** + * A class to uniquely identify users. This combines the id the user has on + * an external service (eg: twitter, facebook) with the provider type. + */ +public class UserId implements java.io.Serializable { + /** + * The id the user has in a external service. + */ + public String id; + + /** + * The provider this user belongs to. + */ + public ProviderType provider; +} diff --git a/app/securesocial/provider/UserService.java b/app/securesocial/provider/UserService.java new file mode 100644 index 000000000..fb951afb8 --- /dev/null +++ b/app/securesocial/provider/UserService.java @@ -0,0 +1,139 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider; + +/** + * A class that provides the means to find save and create users + * for the SecureSocial Module. + * + * @see DefaultUserService + */ +public class UserService { + /** + * This is the interface that defines the behaviour for UserService. + * There is a default implementation in the DefaultUserService class that + * stores things in a map. This is just to provide an example, as a real + * implementation should persist things in a database + * + * @see DefaultUserService + */ + public interface Service { + /** + * Finds a SocialUser that matches the id + * + * @param id A UserId object + * @return A SocialUser instance or null if no user matches the specified id. + */ + SocialUser find(UserId id); + + /** + * Saves the user in the backing store. + * + * @param user A SocialUser object + */ + void save(SocialUser user); + + /** + * Creates an activation request. This is needed for users that + * are creating an account in the system instead of using one in a 3rd party system. + * + * @param user The user that needs to be activated + * @return A string with a uuid that will be embedded in the welcome email. + */ + String createActivation(SocialUser user); + + /** + * Activates a user by setting the isEmailVerified field to true. This is only used + * for UsernamePassword accounts. + * + * @param uuid The uuid created using the createActivation method. + * @return Returns true if the user was activated - false otherwise. + */ + boolean activate(String uuid); + + /** + * This method deletes activations that were not completed by the user (The user did not follow the link + * in the welcome email). + * + * The method should delete the information store for the user too. + * store for the user. + */ + void deletePendingActivations(); + } + + private static Service service; + + /** + * Sets the Service implementation that will be used. + * + * @param delegate A Service instance. + * @See securesocial.jobs.Bootstrap + */ + public static void setService(Service delegate) { + service = delegate; + } + + /** + * @see securesocial.provider.UserService.Service#find(UserId) + * + */ + public static SocialUser find(UserId id) { + checkIsInitialized(); + return service.find(id); + } + + private static void checkIsInitialized() { + if( service == null ) { + throw new RuntimeException("UserService was not properly initialized."); + } + } + + /** + * @see securesocial.provider.UserService.Service#save(SocialUser) + * + */ + public static void save(SocialUser user) { + checkIsInitialized(); + service.save(user); + } + + /** + * @see securesocial.provider.UserService.Service#createActivation(SocialUser) + * + */ + public static String createActivation(SocialUser user) { + return service.createActivation(user); + } + + /** + * @see securesocial.provider.UserService.Service#activate(String) + * + */ + public static boolean activate(String uuid) { + checkIsInitialized(); + return service.activate(uuid); + } + + /** + * @see securesocial.provider.UserService.Service#deletePendingActivations() + * + */ + public static void deletePendingActivations() { + checkIsInitialized(); + service.deletePendingActivations(); + } +} diff --git a/app/securesocial/provider/providers/FacebookProvider.java b/app/securesocial/provider/providers/FacebookProvider.java new file mode 100644 index 000000000..8a8f3e2fd --- /dev/null +++ b/app/securesocial/provider/providers/FacebookProvider.java @@ -0,0 +1,61 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider.providers; + +import com.google.gson.JsonObject; +import play.Logger; +import play.libs.WS; +import securesocial.provider.*; + +import java.util.Map; + +/** + * A Facebook Provider + */ +public class FacebookProvider extends OAuth2Provider +{ + private static final String ME_API = "https://graph.facebook.com/me?fields=name,picture,email&access_token=%s"; + private static final String ERROR = "error"; + private static final String MESSAGE = "message"; + private static final String TYPE = "type"; + private static final String ID = "id"; + private static final String NAME = "name"; + private static final String PICTURE = "picture"; + private static final String EMAIL = "email"; + + public FacebookProvider() { + super(ProviderType.facebook); + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + JsonObject me = WS.url(ME_API, user.accessToken).get().getJson().getAsJsonObject(); + JsonObject error = me.getAsJsonObject(ERROR); + + if ( error != null ) { + final String message = error.get(MESSAGE).getAsString(); + final String type = error.get(TYPE).getAsString(); + Logger.error("Error retrieving profile information from Facebook. Error type: %s, message: %s.", type, message); + throw new AuthenticationException(); + } + + user.id.id = me.get(ID).getAsString(); + user.displayName = me.get(NAME).getAsString(); + user.avatarUrl = me.get(PICTURE).getAsString(); + user.email = me.get(EMAIL).getAsString(); + } +} diff --git a/app/securesocial/provider/providers/FoursquareProvider.java b/app/securesocial/provider/providers/FoursquareProvider.java new file mode 100644 index 000000000..2e2c944d1 --- /dev/null +++ b/app/securesocial/provider/providers/FoursquareProvider.java @@ -0,0 +1,91 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package securesocial.provider.providers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import play.Logger; +import play.libs.WS; +import securesocial.provider.*; + +import java.util.Map; + +/** + * A provider for Foursquare + */ +public class FoursquareProvider extends OAuth2Provider { + private static final String SELF_API = "https://api.foursquare.com/v2/users/self?oauth_token=%s"; + private static final String META = "meta"; + private static final String CODE = "code"; + private static final String ERROR_TYPE = "errorType"; + private static final String ERROR_DETAIL = "errorDetail"; + private static final String RESPONSE = "response"; + private static final String USER = "user"; + private static final String ID = "id"; + private static final String FIRST_NAME = "firstName"; + private static final String LAST_NAME = "lastName"; + private static final String SPACE = " "; + private static final String PHOTO = "photo"; + private static final String CONTACT = "contact"; + private static final String EMAIL = "email"; + + public FoursquareProvider() { + super(ProviderType.foursquare); + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + JsonObject me = WS.url(SELF_API, user.accessToken).get().getJson().getAsJsonObject(); + JsonObject meta = me.getAsJsonObject(META); + + if ( meta.get(CODE).getAsInt() != 200 ) { + final String errorType = meta.get(ERROR_TYPE).getAsString(); + final String errorDetail = meta.get(ERROR_DETAIL).getAsString(); + Logger.error("Error retrieving profile information from Foursquare. Error type: %s, detail: %s.", errorType, errorDetail); + throw new AuthenticationException(); + } + + JsonObject response = me.getAsJsonObject(RESPONSE); + if ( response == null ) { + throw new AuthenticationException(); + } + + JsonObject userInfo = response.getAsJsonObject(USER); + if( userInfo == null ) { + throw new AuthenticationException(); + } + + user.id.id = userInfo.get(ID).getAsString(); + user.displayName = userInfo.get(FIRST_NAME).getAsString(); + final JsonElement lastName = userInfo.get(LAST_NAME); + if ( lastName != null ) { + user.displayName = fullName(user.displayName, lastName.getAsString()); + } + user.avatarUrl = userInfo.get(PHOTO).getAsString(); + final JsonObject contact = userInfo.getAsJsonObject(CONTACT); + if ( contact != null ) { + final JsonElement userEmail = contact.get(EMAIL); + if ( userEmail != null ) { + user.email = userEmail.getAsString(); + } + } + } + + static String fullName(String first, String last) { + return new StringBuilder(first).append(SPACE).append(last).toString(); + } +} diff --git a/app/securesocial/provider/providers/GoogleProvider.java b/app/securesocial/provider/providers/GoogleProvider.java new file mode 100644 index 000000000..ba0a58868 --- /dev/null +++ b/app/securesocial/provider/providers/GoogleProvider.java @@ -0,0 +1,70 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package securesocial.provider.providers; + +import com.google.gson.JsonElement; +import play.libs.OpenID; +import play.libs.WS; +import securesocial.provider.*; + +import java.util.Map; + +/** + * A Google provider that implements OpenID 2 + OAuth extensions. + * In a single flow the user gets authenticated and a token that can be used to invoke + * Google's APIs is retrieved. + */ +public class GoogleProvider extends OpenIDOAuthHybridProvider { + private static final String USER_FORMAT = "https://www.google.com/accounts/o8/id"; + private static final String EMAIL = "email"; + private static final String FIRST_NAME = "firstName"; + private static final String LAST_NAME = "lastName"; + private static final String HTTP_AXSCHEMA_ORG_CONTACT_EMAIL = "http://axschema.org/contact/email"; + private static final String HTTP_AXSCHEMA_ORG_NAME_PERSON_FIRST = "http://axschema.org/namePerson/first"; + private static final String HTTP_AXSCHEMA_ORG_NAME_PERSON_LAST = "http://axschema.org/namePerson/last"; + private static final String SELF_API = "https://www-opensocial.googleusercontent.com/api/people/@me/@self"; + private static final String ENTRY = "entry"; + private static final String THUMBNAIL_URL = "thumbnailUrl"; + + public GoogleProvider() { + super(ProviderType.google, USER_FORMAT); + authMethod = AuthenticationMethod.OPENID_OAUTH_HYBRID; + } + + @Override + protected void configure(OpenID openId) { + openId.required(EMAIL, HTTP_AXSCHEMA_ORG_CONTACT_EMAIL); + openId.required(FIRST_NAME, HTTP_AXSCHEMA_ORG_NAME_PERSON_FIRST); + openId.required(LAST_NAME, HTTP_AXSCHEMA_ORG_NAME_PERSON_LAST); + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + OpenID.UserInfo info = (OpenID.UserInfo) authContext.get(OpenIDProvider.USER_INFO); + user.displayName = FoursquareProvider.fullName(info.extensions.get(FIRST_NAME),info.extensions.get(LAST_NAME)); + user.email = info.extensions.get(EMAIL); + + WS.HttpResponse response = WS.url(SELF_API).oauth(user.serviceInfo, user.token, user.secret).get(); + if ( response.getStatus() != 200 ) { + // Amazingly, if there's an error Google replies with an html page ... if it were json I could + // log some info. + throw new AuthenticationException(); + } + JsonElement contactInfo = response.getJson(); + user.avatarUrl = contactInfo.getAsJsonObject().getAsJsonObject(ENTRY).get(THUMBNAIL_URL).getAsString(); + } +} diff --git a/app/securesocial/provider/providers/LinkedInProvider.java b/app/securesocial/provider/providers/LinkedInProvider.java new file mode 100644 index 000000000..ad2ca31e9 --- /dev/null +++ b/app/securesocial/provider/providers/LinkedInProvider.java @@ -0,0 +1,65 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package securesocial.provider.providers; + +import com.google.gson.JsonObject; +import play.Logger; +import play.libs.WS; +import securesocial.provider.*; + +import java.util.Map; + +/** + * A provider for LinkedIn + */ +public class LinkedInProvider extends OAuth1Provider +{ + private static final String ME_API = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,picture-url)?format=json"; + private static final String ERROR_CODE = "errorCode"; + private static final String MESSAGE = "message"; + private static final String REQUEST_ID = "requestId"; + private static final String TIMESTAMP = "timestamp"; + private static final String ID = "id"; + private static final String FIRST_NAME = "firstName"; + private static final String LAST_NAME = "lastName"; + private static final String PICTURE_URL = "pictureUrl"; + + public LinkedInProvider() { + super(ProviderType.linkedin); + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + JsonObject me = WS.url(ME_API).oauth(user.serviceInfo,user.token, user.secret).get().getJson().getAsJsonObject(); + + if ( me.get(ERROR_CODE) != null ) { + int errorCode = me.get(ERROR_CODE).getAsInt(); + final String message = me.get(MESSAGE).getAsString(); + final String requestId = me.get(REQUEST_ID).getAsString(); + final String timestamp = me.get(TIMESTAMP).getAsString(); + Logger.error("Error retrieving profile information from LinkedIn. Error code: %s, message: %s, requestId: %s, timestamp: %s.", + errorCode, message, requestId, timestamp); + throw new AuthenticationException(); + } + user.id.id = me.get(ID).getAsString(); + user.displayName = FoursquareProvider.fullName(me.get(FIRST_NAME).getAsString(),me.get(LAST_NAME).getAsString()); + user.avatarUrl = me.get(PICTURE_URL).getAsString(); + + // can't get the email because the LinkedIn API does not provide it. + //user.email = ; + } +} diff --git a/app/securesocial/provider/providers/MyOpenIDProvider.java b/app/securesocial/provider/providers/MyOpenIDProvider.java new file mode 100644 index 000000000..14f0c753b --- /dev/null +++ b/app/securesocial/provider/providers/MyOpenIDProvider.java @@ -0,0 +1,58 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ +package securesocial.provider.providers; + +import play.libs.OpenID; +import securesocial.provider.OpenIDProvider; +import securesocial.provider.ProviderType; +import securesocial.provider.SocialUser; + +import java.util.Map; + +/** + * A provider for MyOpenID + */ +public class MyOpenIDProvider extends OpenIDProvider{ + private static final String USER_FORMAT = "http://{username}.myopenid.com/"; + private static final String FULL_NAME = "fullName"; + private static final String EMAIL = "email"; + private static final String IMAGE = "image"; + private static final String HTTP_SCHEMA_OPENID_NET_NAME_PERSON = "http://schema.openid.net/namePerson"; + private static final String HTTP_SCHEMA_OPENID_NET_CONTACT_EMAIL = "http://schema.openid.net/contact/email"; + private static final String HTTP_SCHEMA_OPENID_NET_MEDIA_IMAGE_DEFAULT = "http://schema.openid.net/media/image/default"; + + public MyOpenIDProvider() { + super(ProviderType.myopenid, USER_FORMAT); + } + + @Override + protected void configure(OpenID openId) { + openId.required(FULL_NAME, HTTP_SCHEMA_OPENID_NET_NAME_PERSON); + openId.required(EMAIL, HTTP_SCHEMA_OPENID_NET_CONTACT_EMAIL); + //todo: myopenid is not returning the image ... not supported or wrong type? + openId.required(IMAGE, HTTP_SCHEMA_OPENID_NET_MEDIA_IMAGE_DEFAULT); + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + OpenID.UserInfo me = (OpenID.UserInfo) authContext.get(OpenIDProvider.USER_INFO); + user.displayName = me.extensions.get(FULL_NAME); + user.email = me.extensions.get(EMAIL); + user.avatarUrl = me.extensions.get(IMAGE); + } +} diff --git a/app/securesocial/provider/providers/TwitterProvider.java b/app/securesocial/provider/providers/TwitterProvider.java new file mode 100644 index 000000000..b9cc60820 --- /dev/null +++ b/app/securesocial/provider/providers/TwitterProvider.java @@ -0,0 +1,56 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider.providers; + +import com.google.gson.JsonObject; +import play.Logger; +import play.libs.WS; +import securesocial.provider.*; + +import java.util.Map; + +/** + * A Twitter Provider + */ +public class TwitterProvider extends OAuth1Provider +{ + private static final String VERIFY_CREDENTIALS = "https://api.twitter.com/1/account/verify_credentials.json"; + private static final String ERROR = "error"; + private static final String ID = "id"; + private static final String NAME = "name"; + private static final String PROFILE_IMAGE_URL = "profile_image_url"; + + public TwitterProvider() { + super(ProviderType.twitter); + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + JsonObject me = WS.url(VERIFY_CREDENTIALS).oauth(user.serviceInfo, user.token, user.secret).get().getJson().getAsJsonObject(); + + if ( me.get(ERROR) != null ) { + Logger.error("Error retrieving profile information from Twitter. Error: %s", me.get(ERROR).getAsString()); + throw new AuthenticationException(); + } + user.id.id = me.get(ID).getAsString(); + user.displayName = me.get(NAME).getAsString(); + user.avatarUrl = me.get(PROFILE_IMAGE_URL).getAsString(); + + // can't get the email because the Twitter API does not provide it. + //user.email = ; + } +} diff --git a/app/securesocial/provider/providers/UsernamePasswordProvider.java b/app/securesocial/provider/providers/UsernamePasswordProvider.java new file mode 100644 index 000000000..083e974bc --- /dev/null +++ b/app/securesocial/provider/providers/UsernamePasswordProvider.java @@ -0,0 +1,101 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package securesocial.provider.providers; + +import controllers.securesocial.SecureSocial; +import play.data.validation.Validation; +import play.i18n.Messages; +import play.libs.Crypto; +import play.mvc.Scope; +import securesocial.provider.*; + +import java.util.Map; + +/** + * A provider for username and password authentication + */ +public class UsernamePasswordProvider extends IdentityProvider +{ + private static final String USER_NAME = "userName"; + private static final String PASSWORD = "password"; + private static final String SECURESOCIAL_REQUIRED = "securesocial.required"; + private static final String SECURESOCIAL_BAD_USER_PASSWORD_COMBINATION = "securesocial.badUserPasswordCombination"; + private static final String SECURESOCIAL_ACCOUNT_NOT_ACTIVE = "securesocial.accountNotActive"; + private static final String SECURESOCIAL_WRONG_USER_PASS = "securesocial.wrongUserPass"; + + public UsernamePasswordProvider() { + super(ProviderType.userpass, AuthenticationMethod.USER_PASSWORD); + } + + @Override + protected SocialUser doAuth(Map authContext) { + // + final String userName = Scope.Params.current().get(USER_NAME); + final String password = Scope.Params.current().get(PASSWORD); + + boolean hasErrors = false; + Validation validation = Validation.current(); + if ( userName == null || userName.trim().length() == 0 ) { + validation.addError(USER_NAME, Messages.get(SECURESOCIAL_REQUIRED)); + hasErrors = true; + } + + if ( password == null || password.trim().length() == 0 ) { + validation.addError(PASSWORD, Messages.get(SECURESOCIAL_REQUIRED)); + hasErrors = true; + } + + if ( hasErrors ) { + Scope.Flash.current().put(USER_NAME, userName); + validation.keep(); + SecureSocial.login(); + } + // + UserId id = new UserId(); + id.id = Scope.Params.current().get(USER_NAME); + id.provider = ProviderType.userpass; + SocialUser user = UserService.find(id); + + Scope.Flash flash = Scope.Flash.current(); + + if ( user == null ) { + flash.error(Messages.get(SECURESOCIAL_BAD_USER_PASSWORD_COMBINATION)); + SecureSocial.login(); + } + + if ( !user.isEmailVerified) { + flash.error(Messages.get(SECURESOCIAL_ACCOUNT_NOT_ACTIVE)); + SecureSocial.login(); + } + + if ( user == null || !passwordMatches(Scope.Params.current().get(PASSWORD), user.password)) { + flash.error(Messages.get(SECURESOCIAL_WRONG_USER_PASS)); + SecureSocial.login(); + } + return user; + } + + private boolean passwordMatches(String password, String userPassword) { + return Crypto.passwordHash(password).equals(userPassword); + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + // there's nothing to do here, since the user is being loaded from the DB it should already have + // all the required fields set. + } +} diff --git a/app/securesocial/provider/providers/WordpressProvider.java b/app/securesocial/provider/providers/WordpressProvider.java new file mode 100644 index 000000000..b8dd53266 --- /dev/null +++ b/app/securesocial/provider/providers/WordpressProvider.java @@ -0,0 +1,89 @@ +/** + * Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ +package securesocial.provider.providers; + +import play.libs.OpenID; +import play.libs.WS; +import securesocial.provider.OpenIDProvider; +import securesocial.provider.ProviderType; +import securesocial.provider.SocialUser; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; + +/** + * A Wordpress Provider + * + */ +public class WordpressProvider extends OpenIDProvider +{ + private static final String USER_FORMAT = "http://{username}.wordpress.com"; + private static final String EMAIL = "email"; + private static final String FULLNAME = "fullname"; + private static final String GRAVATAR_URL = "http://www.gravatar.com/avatar/"; + private static final String D_404 = "?d=404"; + private static final String MD5 = "MD5"; + + public WordpressProvider() { + super(ProviderType.wordpress, USER_FORMAT); + } + + + @Override + protected void configure(OpenID openId) { + openId.required(EMAIL); + openId.required(FULLNAME); + + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + OpenID.UserInfo me = (OpenID.UserInfo) authContext.get(OpenIDProvider.USER_INFO); + user.displayName = me.extensions.get(FULLNAME); + user.email = me.extensions.get(EMAIL); + + String hash = gravatarHash(user.email); + if ( hash != null ) { + StringBuilder sb = new StringBuilder(GRAVATAR_URL).append(hash); + String gravatar = sb.toString(); + WS.HttpResponse response = WS.url(sb.append(D_404).toString()).get(); + if ( response.success() ) { + user.avatarUrl = gravatar; + } + } + } + + private String gravatarHash(String email) { + String result = null; + + email = email.trim().toLowerCase(); + if ( email.length() > 0 ) { + try { + MessageDigest m = MessageDigest.getInstance(MD5); + byte[] out = m.digest(email.getBytes()); + result = new BigInteger(1, out).toString(16); + } catch (NoSuchAlgorithmException e) { + // ignore + } + } + + return result; + } +} diff --git a/app/securesocial/provider/providers/YahooProvider.java b/app/securesocial/provider/providers/YahooProvider.java new file mode 100644 index 000000000..08e3a4053 --- /dev/null +++ b/app/securesocial/provider/providers/YahooProvider.java @@ -0,0 +1,58 @@ +/** +* Copyright 2011 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ +package securesocial.provider.providers; + +import play.libs.OpenID; +import securesocial.provider.OpenIDOAuthHybridProvider; +import securesocial.provider.OpenIDProvider; +import securesocial.provider.ProviderType; +import securesocial.provider.SocialUser; + +import java.util.Map; + +/** + * An Openid provider for Yahoo + */ +public class YahooProvider extends OpenIDOAuthHybridProvider +{ + private static final String USER_FORMAT = "https://me.yahoo.com"; + private static final String EMAIL = "email"; + private static final String FULLNAME = "fullname"; + private static final String IMAGE = "image"; + private static final String HTTP_AXSCHEMA_ORG_CONTACT_EMAIL = "http://axschema.org/contact/email"; + private static final String HTTP_AXSCHEMA_ORG_NAME_PERSON = "http://axschema.org/namePerson"; + private static final String HTTP_AXSCHEMA_ORG_MEDIA_IMAGE_DEFAULT = "http://axschema.org/media/image/default"; + + public YahooProvider() { + super(ProviderType.yahoo, USER_FORMAT); + } + + @Override + protected void configure(OpenID openId) { + openId.required(EMAIL, HTTP_AXSCHEMA_ORG_CONTACT_EMAIL); + openId.required(FULLNAME, HTTP_AXSCHEMA_ORG_NAME_PERSON); + openId.required(IMAGE, HTTP_AXSCHEMA_ORG_MEDIA_IMAGE_DEFAULT); + } + + @Override + protected void fillProfile(SocialUser user, Map authContext) { + OpenID.UserInfo me = (OpenID.UserInfo) authContext.get(OpenIDProvider.USER_INFO); + user.displayName = me.extensions.get(FULLNAME); + user.avatarUrl = me.extensions.get(IMAGE); + user.email = me.extensions.get(EMAIL); + } +} diff --git a/app/views/securesocial/Mails/sendActivationEmail.html b/app/views/securesocial/Mails/sendActivationEmail.html new file mode 100644 index 000000000..c2686d297 --- /dev/null +++ b/app/views/securesocial/Mails/sendActivationEmail.html @@ -0,0 +1,6 @@ + + +

&{'securesocial.greeting', user.displayName}

+

&{'securesocial.emailText', activationUrl}

+ + diff --git a/app/views/securesocial/SecureSocial/login.html b/app/views/securesocial/SecureSocial/login.html new file mode 100644 index 000000000..a16c61503 --- /dev/null +++ b/app/views/securesocial/SecureSocial/login.html @@ -0,0 +1,96 @@ +#{extends 'securesocial/SecureSocial/main.html'/} +#{set title: messages.get('securesocial.loginTitle') /} + + + +
+ + + #{if flash.error} +
+
+ ${flash.error} +
+
+ #{/if} + +
+

&{'securesocial.clickOnYourAccount'}

+ +

+ #{list items: providers, as: 'provider'} + #{if provider.type != securesocial.provider.ProviderType.userpass} + #{if (provider.authMethod == securesocial.provider.AuthenticationMethod.OPENID || + provider.authMethod == securesocial.provider.AuthenticationMethod.OPENID_OAUTH_HYBRID) && + provider.needsUsername() } + + #{/if} + #{else} + + #{/else} + + #{/if} + #{/list} +

+
+ + + + #{if userPassEnabled } +
+

&{'securesocial.useEmailAndPassword'}

+ #{form @securesocial.SecureSocial.authenticate(securesocial.provider.ProviderType.userpass), autocomplete:'off'} +
+
+ +
+ + #{ifError 'userName'} + #{error 'userName'/} + #{/ifError} +
+
+ +
+ +
+ + #{ifError 'password'} + &{'securesocial.required'} + #{/ifError} +
+
+ +
+ +
+
+ #{/form} +
+ +
+

&{'securesocial.signUp'} #{a @securesocial.UsernamePasswordController.signUp()}&{'securesocial.here'}#{/a}

+
+ #{/if} +
+ diff --git a/app/views/securesocial/SecureSocial/main.html b/app/views/securesocial/SecureSocial/main.html new file mode 100644 index 000000000..dc91418e9 --- /dev/null +++ b/app/views/securesocial/SecureSocial/main.html @@ -0,0 +1,30 @@ + + + + + #{get 'title' /} + + + #{get 'moreStyles' /} + + #{get 'moreScripts' /} + + +
+ +
+
+
+
+ #{doLayout /} +
+
+
+ + + diff --git a/app/views/securesocial/SecureSocial/noticePage.html b/app/views/securesocial/SecureSocial/noticePage.html new file mode 100644 index 000000000..241c8f5b5 --- /dev/null +++ b/app/views/securesocial/SecureSocial/noticePage.html @@ -0,0 +1,16 @@ +#{extends 'securesocial/SecureSocial/main.html'/} + +
+ + +
+
+

+ #{if flash.error}${flash.error}#{/if} + #{else}${flash.success.raw()}#{/else} +

+
+
+
diff --git a/app/views/securesocial/UsernamePasswordController/signup.html b/app/views/securesocial/UsernamePasswordController/signup.html new file mode 100644 index 000000000..a75958002 --- /dev/null +++ b/app/views/securesocial/UsernamePasswordController/signup.html @@ -0,0 +1,83 @@ +#{extends 'securesocial/SecureSocial/main.html'/} +#{set title: messages.get('securesocial.signUpTitle') /} + +
+ + + #{if flash.error} +
+
+ ${flash.error} +
+
+ #{/if} + +
+

&{'securesocial.signUpLabel'}

+ #{form @securesocial.UsernamePasswordController.createAccount(), autocomplete:'off'} +
+
+ +
+ + #{ifError 'userName'} + #{error 'userName'/} + #{/ifError} +
+
+ +
+ +
+ + #{ifError 'displayName'} + &{'securesocial.required'} + #{/ifError} +
+
+ +
+ +
+ + + #{ifError 'email'} + #{error 'email'/} + #{/ifError} + #{else} + &{'securesocial.emailHelp'} + #{/else} + +
+
+ +
+ +
+ + #{ifError 'password'} + &{'securesocial.required'} + #{/ifError} +
+
+ +
+ +
+ + #{ifError 'password2'} + #{error 'password2'/} + #{/ifError} +
+
+ + +
+ #{/form} +
+
\ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..4526d6d9d --- /dev/null +++ b/build.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+ + + + + + + + +
diff --git a/commands.py b/commands.py new file mode 100644 index 000000000..28de44772 --- /dev/null +++ b/commands.py @@ -0,0 +1,35 @@ +# Here you can create play commands that are specific to the module, and extend existing commands + +MODULE = 'securesocial' + +# Commands that are specific to your module + +COMMANDS = ['securesocial:hello'] + +def execute(**kargs): + command = kargs.get("command") + app = kargs.get("app") + args = kargs.get("args") + env = kargs.get("env") + + if command == "securesocial:hello": + print "~ Hello" + + +# This will be executed before any command (new, run...) +def before(**kargs): + command = kargs.get("command") + app = kargs.get("app") + args = kargs.get("args") + env = kargs.get("env") + + +# This will be executed after any command (new, run...) +def after(**kargs): + command = kargs.get("command") + app = kargs.get("app") + args = kargs.get("args") + env = kargs.get("env") + + if command == "new": + pass diff --git a/commands.pyc b/commands.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3bf52a5e14b6daa19766209c6584aa4bebfb362 GIT binary patch literal 1100 zcmcIiOHaZ;5T0!dQk0k&j~+a7qIY8qMvNp%iA(Un4cn!HK%4I3BSA0v2mL3;gJ)+3 z5F>bj*k)(vu{%58H}m#U$bY^kegM-?;9kb1ODIBs7D@q;fR=#Jf>-od5ZbKR5IU?l z5N4oJ%;FWZMVD1DN|la-Zo98{emM?qRKFkdgBc82xX$Uw6^b!Hmmho)@FZrY1!P(? z(`M72nGTx{jIqEB^el*75MdV1szZSaU|}1W+BYPFcra)WLt~@fP7>qLo<^D-YIyfY zwVLie?^mhvB2mQWBV~#N!H~G6G?HSHAW3>bQu-pUiC|8>rdFx;dT|;lFG=H`3XJxC zGUiQ-)TQK+i&hWFr%^8E(L^tZ?a6Ny#3GMnJg^Rr%ori^m!e^W6lUm>dcO1=`d3Fy zS+7+mPSq}II!DQ-pcesOVsIV6^TBaIe9sJ(u#^FA{ZQTW9LWD>g7UXJMx}eGEOg(W z%pcWSt5=#Ar-rCHsn=@9ex)I4Y9)DO9>shRSzBcqDc9j34o7`;L<7#9$vUp(Zo0WI D!k5Q+ literal 0 HcmV?d00001 diff --git a/conf/dependencies.yml b/conf/dependencies.yml new file mode 100644 index 000000000..2ac0a9a6e --- /dev/null +++ b/conf/dependencies.yml @@ -0,0 +1,4 @@ +self: play -> securesocial 0.1 + +require: + - play diff --git a/conf/messages b/conf/messages new file mode 100644 index 000000000..5efd8093e --- /dev/null +++ b/conf/messages @@ -0,0 +1,47 @@ +# +# Login +# +securesocial.loginTitle=Log In +securesocial.login=Log In +securesocial.clickOnYourAccount=Use your existing account on one of the following networks to log in. +securesocial.useEmailAndPassword=Or use a username and password. +securesocial.email=Email Address +securesocial.emailHelp=Eg: john.doe@gmail.com +securesocial.password=Password +securesocial.signUp=Sign up +securesocial.wrongUserPass=Wrong email address and password combination. +securesocial.required=Required +securesocial.invalidEmail=Enter a valid email address +securesocial.signUp=If you don't have an account with us yet you can sign up +securesocial.here=here +securesocial.badUserPasswordCombination=Wrong username and password combination +# [type] gets replaced by the provider id in the login page. +# can't use %s because the string is being processed in javascript. +securesocial.openIdUser=Username on [type] +securesocial.openIdUserNotSpecified=Username not specified +securesocial.authError=There was an error while trying to log you in. Please try again. + +# +# SignUp +# +securesocial.signUpTitle=Sign Up +securesocial.signUpLabel=Complete the following form: +securesocial.userName=Username +securesocial.name=Full Name +securesocial.password2=Password confirmation +securesocial.cancel=Cancel +securesocial.passwordsMustMatch=The passwords do not match +securesocial.userNameTaken=This username is already taken +securesocial.activationTitle=Welcome %s +securesocial.activationSuccess=Thank you, your account has been activated. Click here to log in. +securesocial.activateTitle=Account Activation +securesocial.errorCreatingAccount=There was an error creating your account. Please try again. +securesocial.accountNotActive=Your account is not active. If you think this is an error please contact the administrator. +securesocial.accountCreated=Your account has been created. You should receive an activation email shortly. +securesocial.invalidLink=The activation link you followed is invalid. + +# +# Welcome email +# +securesocial.greeting=Hello %s, +securesocial.emailText=Follow this link to activate your account. \ No newline at end of file diff --git a/conf/routes b/conf/routes new file mode 100644 index 000000000..8445113f4 --- /dev/null +++ b/conf/routes @@ -0,0 +1,25 @@ +# This file defines all module routes (Higher priority routes first) +# +# import these routes in the main app as : +# * / module:securesocial +# +# ~~~~ + +# +# Username Password Controller routes +# +GET /signup securesocial.UsernamePasswordController.signup +POST /signup securesocial.UsernamePasswordController.createAccount +GET /activate/{uuid} securesocial.UsernamePasswordController.activate + +# +# Secure Social Controller routes +# +GET /login securesocial.SecureSocial.login +GET /logout securesocial.SecureSocial.logout +GET /{type} securesocial.SecureSocial.authenticate + +# +# Static content +# +GET /public/ staticDir:public diff --git a/documentation/images/loginPage.png b/documentation/images/loginPage.png new file mode 100644 index 0000000000000000000000000000000000000000..0182d88d6addc2757606c2bdfe42d660e56f7073 GIT binary patch literal 46762 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�L!bJFPQrm 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�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 literal 0 HcmV?d00001 diff --git a/documentation/manual/home.textile b/documentation/manual/home.textile new file mode 100644 index 000000000..a55865f9b --- /dev/null +++ b/documentation/manual/home.textile @@ -0,0 +1,247 @@ +h1. SecureSocial Module + +SecureSocial allows you to add an authentication UI to your app that works with services based on OAuth1, OAuth2, OpenID and OpenID+OAuth hybrid protocols. + +It also provides a Username and Password mechanism for users that do not wish to use existing accounts in other networks. + +The following services are supported in this release: + +* Twitter (OAuth1) +* Facebook (OAuth2) +* Google (OpenID + OAuth Hybrid) +* Yahoo (OpenID + OAuth Hybrid) +* LinkedIn (OAuth1) +* Foursquare (OAuth2) +* MyOpenID (OpenID) +* Wordpress (OpenID) +* Username and Password + +The module does not depend on any external Java libray. It relies only on what the Play! Framework provides and uses the awesome Bootstrap toolkit from Twitter to style the UI. + +There is a live demo at http://securesocial-jaliss.rhcloud.com. You can try all the providers there except the Username and Password one because the app cannot send emails on that server. + + +Source code is available at https://github.com/jaliss/securesocial +Written by Jorge Aliss (@jaliss) + + +h2. Getting started + +To install SecureSocial use the following command: + +bc. play install securesocial + +Then add it as a dependency in your dependencies.yml file: + +bc. require: + -play -> securesocial {version} + + +h2. Securing a Controller + +To restrict access to authenticated users just add @With( SecureSocial.class ) to your controller: + +bc. @With( SecureSocial.class ) +public class Application extends Controller { + public static void index() { + render(); + } +} + +Trying to access any action on a protected controller will show a login page if the user has not been authenticated yet. + +h2. The Login Page + +On the login page users can select their preferred accout in one of the supported services (Eg: Twitter, Facebook) or use a username/password combination if they don't want to use an existing account. + +!images/loginPage! + +A sign up form is provided to allow users to create an account in your application. + +!images/signUpPage! + +When users create an account they need to verify their email address clicking on a link they receive on a welcome email. + +h2. Configuration + +SecureSocial needs you to provide some settings to work property as described below. + +h3. Identity Providers + +You can customize which identity providers your application uses and the order they appear in the login page as follows: + +bc. securesocial.providers=twitter,facebook,google,yahoo,foursquare,linkedin,wordpress,myopenid,userpass + +For example if you only wanted to use Twitter, Facebook and Google you would set it as: + +bc. securesocial.providers=twitter,facebook,google + +The values for this property come from the ProviderType enum. If you don't set this, SecureSocial will display all the providers in the login page. + +Note: The userpass provider always appears below the icons row. If you need another layout +you'll have to change the login page template. + +h3. Identity Provider settings + +For each OAuth based provider you plan to use you need to register your application at the developer's site of each service and include the corresponding properties in your application.conf. + + +bc. # +# Twitter +# +securesocial.twitter.requestTokenURL=https://twitter.com/oauth/request_token +securesocial.twitter.accessTokenURL=https://twitter.com/oauth/access_token +securesocial.twitter.authorizationURL=https://twitter.com/oauth/authenticate +securesocial.twitter.consumerKey=your_consumer_key +securesocial.twitter.consumerSecret=your_consumer_secret + +bc. # +# Facebook +# +securesocial.facebook.authorizationURL=https://graph.facebook.com/oauth/authorize +securesocial.facebook.accessTokenURL=https://graph.facebook.com/oauth/access_token +securesocial.facebook.clientid=your_client_id +securesocial.facebook.secret=your_client_secret +securesocial.facebook.scope=email + + +bc. # +# Google +# +securesocial.google.requestTokenURL=https://www.google.com/accounts/OAuthGetRequestToken +securesocial.google.accessTokenURL=https://www.google.com/accounts/OAuthGetAccessToken +securesocial.google.authorizationURL=https://www.google.com/accounts/OAuthAuthorizeToken +securesocial.google.scope=http://www-opensocial.googleusercontent.com/api/people +securesocial.google.consumerKey=your_consumer_key +securesocial.google.consumerSecret=your_consumer_secret + +bc. # +# Yahoo +# +securesocial.yahoo.requestTokenURL=https://api.login.yahoo.com/oauth/v2/get_request_token +securesocial.yahoo.accessTokenURL=https://api.login.yahoo.com/oauth/v2/get_token +securesocial.yahoo.authorizationURL=https://api.login.yahoo.com/oauth/v2/request_auth +securesocial.yahoo.consumerKey=your_consumer_key +securesocial.yahoo.consumerSecret=your_consumer_secret + +bc. # +# LinkedIn +# +securesocial.linkedin.requestTokenURL=https://api.linkedin.com/uas/oauth/requestToken +securesocial.linkedin.accessTokenURL=https://api.linkedin.com/uas/oauth/accessToken +securesocial.linkedin.authorizationURL=https://api.linkedin.com/uas/oauth/authenticate +securesocial.linkedin.consumerKey=your_consumer_key +securesocial.linkedin.consumerSecret=your_consumer_secret + +bc. # +# Foursquare +# +securesocial.foursquare.authorizationURL=https://foursquare.com/oauth2/authenticate?response_type=code +securesocial.foursquare.accessTokenURL=https://foursquare.com/oauth2/access_token?grant_type=authorization_code +securesocial.foursquare.clientid=your_client_id +securesocial.foursquare.secret=your_client_secret + +Some providers use a scope property. The values shown above are what SecureSocial needs to provide your app with the name, email and avatar of your users. If you need permissions you can add the required scopes in a comma separated list. + +If you are going to use the UsernamePassword provider set the following properties that are required by the Mailer: + +bc. # +# Mailer +# +securesocial.mailer.subject=Activate your account +securesocial.mailer.from=you@at.your.company.com + +To customize the email body change the views/securesocial/Mails/sendActivationEmail.html template. + +h2. SecureSocial, SocialUser and UserService + +In the protected controllers you can access the current user by executing: + +bc. SocialUser user = SecureSocial.getCurrentUser(); + +The SocialUser object has the following attributes: + +* id: A UserId object instance that has the provider type and the user id on the external service. +* displayName: A string with the user's full name +* email: The user's email address +* avatarUrl: A url that points to the user's avatar. +* authMethod: One of the values defined in the AuthenticationMethod enum. +* lastAccess: A Date with the last login time + +The module tries its best to provide that information however some providers might not make all available. For example Twitter and LinkedIn do not expose the user's email in their APIs. + +Depending on the authMethod the module sets values on different attributes of the user object: + +- AuthenticationMethod.OAuth1 and AuthenticationMethod.OPENID_OAUTH_HYBRID +* serviceInfo: A OAuth.ServiceInfo instance with the values needed for the provider. +* token: A string with the user token +* secret: A string with the user secret + +- AuthenticationMethod.OAuth2 +* accessToken: The access token + +- AuthenticationMethod.USER_PASSWORD +* password: A string with the user password. +* isEmailVerified: A boolean indicating if the user verified the email address or not + +h3. Displaying user information in a template + +SecureSocial adds a user object to the render arguments of each action in a secured controller. To display user information you can do something like: + +bc. +
+

User details

+
    +
  • User id: ${user.id.id}
  • +
  • Logged in from: ${user.id.provider}
  • +
  • Email: ${user.email}
  • +
+
+ +h3. Invoking a service API + +OAuth1 sample: + +bc. SocialUser user = SecureSocial.getCurrentUser(); +JsonObject me = WS.url("http://api.twitter.com/1/account/verify_credentials.json"). +oauth(user.serviceInfo,user.token, user.secret).get().getJson().getAsJsonObject(); + +OAuth2 sample: + +bc. SocialUser user = SecureSocial.getCurrentUser(); +JsonObject me = WS.url("https://graph.facebook.com/me?access_token=%s", user.accessToken). +get().getJson().getAsJsonObject(); + +To invoke the right APIs you need to find out what provider the user belongs to. You can do it as follows: + +bc. SocialUser user = SecureSocial.getCurrentUser(); +if ( user.id.provider == ProviderType.facebook ) { +... +} + +The ProviderType enum has values for all the supported providers. + +h3. UserService + +This module does not persist users in the database. It relies on an implementation of the UserService.Service interface for that. + +There is a default implementation that stores things in a HashMap included that can be used for development mode, however for production use you need to provide a valid implementation within your application. + +The interface defines the following methods: + +bc. public interface Service { + SocialUser find(UserId id); + void save(SocialUser user); + String createActivation(SocialUser user); + boolean activate(String uuid); + void deletePendingActivations(); +} + +The find and save methods are required by all providers. The createActivation, activate and deletePendingActivations methods are used for the Username Password provider. +If you do not plan to use it you can provide empty implementations for those methods. + +When the application starts SecureSocial looks for UserService.Service implementation in the classpath and uses it automatically. You do not need to configure anything else for it to be picked up. + +Note: check the documentation in the source code for more information on these methods. \ No newline at end of file diff --git a/public/images/providers/facebook.png b/public/images/providers/facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..77906a4b0db5d3caf4860e7f1613fc8b2bcce229 GIT binary patch literal 939 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 literal 0 HcmV?d00001 diff --git a/public/images/providers/linkedin.png b/public/images/providers/linkedin.png new file mode 100644 index 0000000000000000000000000000000000000000..2b7c501aa2c72b6c55ce5a5a148b3f2a4ea37305 GIT binary patch literal 1020 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 T_TuS^00000NkvXXu0mjf%}6>s literal 0 HcmV?d00001 diff --git a/public/images/providers/twitter.png b/public/images/providers/twitter.png new file mode 100644 index 0000000000000000000000000000000000000000..eddeec87380d0930486733bbe0ed8bc7e60c84c5 GIT binary patch literal 946 zcmV;j15NyiP)DNZOB0(cmI_t@OT|8Iu+3i}SPK!vGAoH` zG?iM2Ay_CRAwtCHzJy5Ld-vWs$KvJ{n|*igi_6=5VVb#fe$4mG`OaK1Gd{ZU_;hHu=cL`<@+;ef9H1qi(s}K!>nk5rx zE6=6vVXMvWPinsXv*byw$pqAlP+PhLsc&s~a}@9hU)-A{rM+$#NSEOMYQ(9bXIdL; zS^GKBrVA{&%T$M?@awpuj7A7)-<^l>Mcchr`1$XOFYio{tlM{pbTH7Ok{?_fALtGM z=b*@h;hNBMLY-hRE~A7_7T0inAmj_`D}eV9oM5sn1MO?5T2A5CM&LmeTkRAh-GmJT zZq7j4C=K>z2VH%*+2@UZz&h-^Mu;6cpup{M&Dj(AzWL*8j!(7*3vF&cuDEi4%3hV` zAR4%6MrxSA)jy^kWbVgX>lqhb?@`6H?`Ve84vG-CH;H_8=f#rP90tmm_N_)E#Xi;R z0zMP2+@0`frd-(SQTXm#@Rz-cDjJel+ek6#%Lo7mSpiocO!@I)$$O`JY`>E6YVV-X z*9t#9EcyJmrGLp!s9bcr=Z;;kD|R$;^O-oY;t@4+v#!|LJ$LN-fBjh| UZ~>rM(f|Me07*qoM6N<$f-mu|YybcN literal 0 HcmV?d00001 diff --git a/public/images/providers/wordpress.png b/public/images/providers/wordpress.png new file mode 100644 index 0000000000000000000000000000000000000000..28c9953275f6c4885cb069731f4f47f060d24e57 GIT binary patch literal 1757 zcmV<31|s>1P)P^9d=!ooG2vtXIlBI(7Yh|3(8BG;wwWaN(ZKQT<)h6%zK6{ZS^dr>{O!vUK zd3m1m|3Bya&v}oKQc_-CzFpU}ld7gw2q6ktMw3#GnTGKoVHp32L?REwmMvR8cQ_oK zPN!3~*=#6^f)HZ4z_Bcgcs$OVH*d_TsVT4Ka5x(B^72%>-HxVd%LYE1glU@Sx~>8Z zn#1APo&rxDSZ-5Q6}#O|JRaY!>AH@hDE}AW*`_E8x~`)kCDO8BnNvUCq?8~fnv`?pG_A)p$L|0cA4}SV-N%2y+qPDj7wZq}a`Iz`tty;y= zqem$#D+6G7c$o3=aR8i7C+pX*2jJPWXEZi85{*VbN?>YgYC`Pw`94TM2!Y@4=TCtE zmSu6}$`!7)v@kq8Jg01h1qJ-&>#qp}0wfX%E?v6Bk2h{C+Epn6>V^#)zSVX86975S zEh+iyH{TEl1c*c;96o%Q_V#v6)5Pv@ke!{4-EJoL=g;|fclUx!q(x|17M5jYOx3}I2Z3abo0^)4{`MQ^&Yff5zJ2)i?oFS2 z6^n7p z@p@u{fBx_TEiEm$-EQ1&H2iQVBqlAPx~Ge9nv zi?U6dkbp8nE)t0ld-aOZ(NSD37p7^>!ARjM^V4EkRyu{cuA^xh!C)|5h8;V0V47yK zORB0!BoYAB)YM>_CPD~GOG^m`gQ%(sKq8Sy63Bp+Q3Fy+VzC%5mkS{Ty}iBk^z;C* zXU`rC!=Rv`fZW_%u3o*GE{|!Nl$V#2laoU@97a(TRpK$sV7NU@%A|5~07pKbak$k9}W$Noz2O z5Q6&pdI0X;y$eDty$yvBw6(P{GBUz}0|&^@&u4smoX*ZpAUWY24hOxxy=a=o_3PIG zsIRYQ#flY#LLpo(7yI|`XJBA}wzf8a%q7Sqj1;Mch6ZwSa%gO9#OZX>($WIJ)~#E) zefu^-2vk+&=FOXF(RO!t^Y-mqPMtc1-EQa1nKN@@W)$IkXQs`<;V_pkU#7IQl(T2g z(%;|DhSyhOixcUFfc%Kb8~u+v}WeU&Ye4{sHh+}H<#0=PjmkK`Sgz~ z6S!%b(_-7UZLe)MTMij4wZX>YaW-z;$jOr@@pwD{#A31JP5_jZm64T|1;C?6k2rSh z7=wd@*ladbRb6x^h{xj-!sGEQN+3ld^?$G|3!l$NU0oe(*RD;IdHnb>eSLj|LLu7P z+E5fF{qhfOT4@5ht}jYpel`rlNKe94sZv@{RTWLsKKSa{mPjNfe%Df(3an|`f;%Y^ zsYPX}i|OwoFdOJYL6>SN0>4TrcP;tWA81m_U)B8l{6R%g{tPG|z46C_n}%WhO-L!( zym|BIs;V{!Au0gpGGo7yQa&(E^Y1-9Jzf6;lFLHOhr;9t00000NkvXXu0mjfcyT$q literal 0 HcmV?d00001 diff --git a/public/images/providers/yahoo.png b/public/images/providers/yahoo.png new file mode 100644 index 0000000000000000000000000000000000000000..1603a5f0b48a87f4453aac3097f2ffd28796d6b7 GIT binary patch literal 1390 zcmV-!1(EuRP)f079v4(cSLZ-I>paZMQB3sm6l;$w^M;_W#eE z`Tp}Axh8u9j_At7Bx@XQzKCgN451qSs8and4e8nsitm0SH{}*DXtU!#ZMEabN)9V_ zEcx;38PFpAzK>r2+cmDuj_*7@nnH`o!3gpoB3dZiE#>Vq)t6bg-NXI8`O+$zA;rR23Jfl~aL0_#X&R z4Go{bzB&vA1}XfC21<$-Iu}I0A-b7%>>JZ206`&cAvV6Q@jM(u_$Q+;@;POh?> zWA~;#ti8UIq>v$Jniw~&l~wQG$7M_B6P#!J=Q}}$oj27IE~qD{=S0XzOf}8&m3JF=012F`YzQadqL_W~<&33{FF2(66cK|>J zMU)Pt4fSglt|dAWla(xj)aMJq(|@?1L%(#B_LYfD(!izaah_TkEkpz>jq&WtQEhjG zhPf1_XMYb{zuJaiP_&`l<8g}qqr2c(S9cP}P?iz%h!|F1{Ro+C1|w!fyQ0|H(FGk& zqQ8lP;Sva(F=s00T{V-7uN`*2s;`hVWvG-Z6#9lkEzU)(d}Rg2o&w!J_B1S@76mCT zjB*FOdS3ti zye?ocC-i|Zw*OZe4x4^4e9cA10S#Z6VhOfg`>58mH+?%07*qoM6N<$g0)YIkpKVy literal 0 HcmV?d00001 diff --git a/public/stylesheets/bootstrap-1.3.0/bootstrap.min.css b/public/stylesheets/bootstrap-1.3.0/bootstrap.min.css new file mode 100644 index 000000000..b555c6553 --- /dev/null +++ b/public/stylesheets/bootstrap-1.3.0/bootstrap.min.css @@ -0,0 +1,330 @@ +html,body{margin:0;padding:0;} +h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;} +table{border-collapse:collapse;border-spacing:0;} +ol,ul{list-style:none;} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted;} +a:hover,a:active{outline:0;} +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{border:0;-ms-interpolation-mode:bicubic;} +button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;} +button,input{line-height:normal;*overflow:visible;} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} +input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +html,body{background-color:#ffffff;} +body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;} +.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;*display:inline;} +.container:after{clear:both;} +.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;*display:inline;} +.container-fluid:after{clear:both;} +.container-fluid>.sidebar{float:left;width:220px;} +.container-fluid>.content{margin-left:240px;} +a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;*display:inline;} +.row:after{clear:both;} +[class*="span"]{display:inline;float:left;margin-left:20px;} +.span1{width:40px;} +.span2{width:100px;} +.span3{width:160px;} +.span4{width:220px;} +.span5{width:280px;} +.span6{width:340px;} +.span7{width:400px;} +.span8{width:460px;} +.span9{width:520px;} +.span10{width:580px;} +.span11{width:640px;} +.span12{width:700px;} +.span13{width:760px;} +.span14{width:820px;} +.span15{width:880px;} +.span16{width:940px;} +.span17{width:1000px;} +.span18{width:1060px;} +.span19{width:1120px;} +.span20{width:1180px;} +.span21{width:1240px;} +.span22{width:1300px;} +.span23{width:1360px;} +.span24{width:1420px;} +.offset1{margin-left:80px;} +.offset2{margin-left:140px;} +.offset3{margin-left:200px;} +.offset4{margin-left:260px;} +.offset5{margin-left:320px;} +.offset6{margin-left:380px;} +.offset7{margin-left:440px;} +.offset8{margin-left:500px;} +.offset9{margin-left:560px;} +.offset10{margin-left:620px;} +.offset11{margin-left:680px;} +.offset12{margin-left:740px;} +.span-one-third{width:300px;} +.span-two-thirds{width:620px;} +.offset-one-third{margin-left:340px;} +.offset-two-thirds{margin-left:660px;} +p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;} +h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;} +h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;} +h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;} +h3,h4,h5,h6{line-height:36px;} +h3{font-size:18px;}h3 small{font-size:14px;} +h4{font-size:16px;}h4 small{font-size:12px;} +h5{font-size:14px;} +h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;} +ul,ol{margin:0 0 18px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +ul{list-style:disc;} +ol{list-style:decimal;} +li{line-height:18px;color:#808080;} +ul.unstyled{list-style:none;margin-left:0;} +dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;} +dl dt{font-weight:bold;} +dl dd{margin-left:9px;} +hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;} +strong{font-style:inherit;font-weight:bold;} +em{font-style:italic;font-weight:inherit;line-height:inherit;} +.muted{color:#bfbfbf;} +blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;} +blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';} +address{display:block;line-height:18px;margin-bottom:18px;} +code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;} +pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;} +form{margin-bottom:18px;} +fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;} +form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;*display:inline;} +form .clearfix:after{clear:both;} +label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;} +label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;} +form .input{margin-left:150px;} +input[type=checkbox],input[type=radio]{cursor:pointer;} +input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;} +input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;} +select,input[type=file]{height:27px;line-height:27px;*margin-top:4px;} +select[multiple]{height:inherit;} +textarea{height:auto;} +.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} +:-moz-placeholder{color:#bfbfbf;} +::-webkit-input-placeholder{color:#bfbfbf;} +input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);} +input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);} +input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;} +form div.clearfix.error{background:#fae5e3;padding:10px 0;margin:-10px 0 10px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}form div.clearfix.error>label,form div.clearfix.error span.help-inline,form div.clearfix.error span.help-block{color:#9d261d;} +form div.clearfix.error input,form div.clearfix.error textarea{border-color:#c87872;-webkit-box-shadow:0 0 3px rgba(171, 41, 32, 0.25);-moz-box-shadow:0 0 3px rgba(171, 41, 32, 0.25);box-shadow:0 0 3px rgba(171, 41, 32, 0.25);}form div.clearfix.error input:focus,form div.clearfix.error textarea:focus{border-color:#b9554d;-webkit-box-shadow:0 0 6px rgba(171, 41, 32, 0.5);-moz-box-shadow:0 0 6px rgba(171, 41, 32, 0.5);box-shadow:0 0 6px rgba(171, 41, 32, 0.5);} +form div.clearfix.error .input-prepend span.add-on,form div.clearfix.error .input-append span.add-on{background:#f4c8c5;border-color:#c87872;color:#b9554d;} +.input-mini,input.mini,textarea.mini,select.mini{width:60px;} +.input-small,input.small,textarea.small,select.small{width:90px;} +.input-medium,input.medium,textarea.medium,select.medium{width:150px;} +.input-large,input.large,textarea.large,select.large{width:210px;} +.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;} +.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;} +textarea.xxlarge{overflow-y:auto;} +input.span1,textarea.span1,select.span1{display:inline-block;float:none;width:30px;margin-left:0;} +input.span2,textarea.span2,select.span2{display:inline-block;float:none;width:90px;margin-left:0;} +input.span3,textarea.span3,select.span3{display:inline-block;float:none;width:150px;margin-left:0;} +input.span4,textarea.span4,select.span4{display:inline-block;float:none;width:210px;margin-left:0;} +input.span5,textarea.span5,select.span5{display:inline-block;float:none;width:270px;margin-left:0;} +input.span6,textarea.span6,select.span6{display:inline-block;float:none;width:330px;margin-left:0;} +input.span7,textarea.span7,select.span7{display:inline-block;float:none;width:390px;margin-left:0;} +input.span8,textarea.span8,select.span8{display:inline-block;float:none;width:450px;margin-left:0;} +input.span9,textarea.span9,select.span9{display:inline-block;float:none;width:510px;margin-left:0;} +input.span10,textarea.span10,select.span10{display:inline-block;float:none;width:570px;margin-left:0;} +input.span11,textarea.span11,select.span11{display:inline-block;float:none;width:630px;margin-left:0;} +input.span12,textarea.span12,select.span12{display:inline-block;float:none;width:690px;margin-left:0;} +input.span13,textarea.span13,select.span13{display:inline-block;float:none;width:750px;margin-left:0;} +input.span14,textarea.span14,select.span14{display:inline-block;float:none;width:810px;margin-left:0;} +input.span15,textarea.span15,select.span15{display:inline-block;float:none;width:870px;margin-left:0;} +input.span16,textarea.span16,select.span16{display:inline-block;float:none;width:930px;margin-left:0;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;} +.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;} +.help-inline,.help-block{font-size:11px;line-height:18px;color:#bfbfbf;} +.help-inline{padding-left:5px;*position:relative;*top:-5px;} +.help-block{display:block;max-width:600px;} +.inline-inputs{color:#808080;}.inline-inputs span,.inline-inputs input{display:inline-block;} +.inline-inputs input.mini{width:60px;} +.inline-inputs input.small{width:90px;} +.inline-inputs span{padding:0 2px 0 1px;} +.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;} +.input-prepend .add-on{*margin-top:1px;} +.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;} +.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;} +.inputs-list label{display:block;float:none;width:auto;padding:0;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;} +.inputs-list label small{font-size:11px;font-weight:normal;} +.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;} +.inputs-list:first-child{padding-top:6px;} +.inputs-list li+li{padding-top:2px;} +.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;} +.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;} +.form-stacked legend{padding-left:0;} +.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;} +.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;} +.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;} +.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;} +.form-stacked .actions{margin-left:-20px;padding-left:20px;} +table{width:100%;margin-bottom:18px;padding:0;border-collapse:separate;*border-collapse:collapse;font-size:13px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;} +table th{padding-top:9px;font-weight:bold;vertical-align:middle;border-bottom:1px solid #ddd;} +table td{vertical-align:top;} +table th+th,table td+td{border-left:1px solid #ddd;} +table tr+tr td{border-top:1px solid #ddd;} +table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} +table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} +table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} +table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} +.zebra-striped tbody tr:nth-child(odd) td{background-color:#f9f9f9;} +.zebra-striped tbody tr:hover td{background-color:#f5f5f5;} +table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;} +table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);} +table .header:hover:after{visibility:visible;} +table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} +table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} +table .blue{color:#049cdb;border-bottom-color:#049cdb;} +table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;} +table .green{color:#46a546;border-bottom-color:#46a546;} +table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;} +table .red{color:#9d261d;border-bottom-color:#9d261d;} +table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;} +table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;} +table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;} +table .orange{color:#f89406;border-bottom-color:#f89406;} +table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;} +table .purple{color:#7a43b6;border-bottom-color:#7a43b6;} +table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;} +.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.topbar h3 a:hover,.topbar .brand a:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;} +.topbar h3{position:relative;} +.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;} +.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;} +.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.topbar form.pull-right{float:right;} +.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;} +.topbar input::-webkit-input-placeholder{color:#e6e6e6;} +.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;} +.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);} +.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} +.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;} +.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;} +.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);} +.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;} +.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);} +.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);} +.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;} +.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;} +.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;} +.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;} +li.menu,.dropdown{position:relative;} +a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"↓";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;} +.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;} +.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);} +.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);} +.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;} +.tabs,.pills{margin:0 0 20px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;*display:inline;} +.tabs:after,.pills:after{clear:both;} +.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;} +.tabs{float:left;width:100%;border-bottom:1px solid #ddd;}.tabs>li{position:relative;top:1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:36px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;} +.tabs>li.active>a{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;} +.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} +.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;} +.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;} +.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;} +.tab-content{clear:both;} +.pills a{margin:5px 3px 5px 0;padding:0 15px;text-shadow:0 1px 1px #ffffff;line-height:30px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{background:#00438a;color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);} +.pills .active a{background:#0069d6;color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);} +.tab-content>*,.pill-content>*{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.breadcrumb{margin:0 0 18px;padding:7px 14px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;} +.breadcrumb .divider{padding:0 5px;color:#bfbfbf;} +.breadcrumb .active a{color:#404040;} +.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;} +.hero-unit p{font-size:18px;font-weight:200;line-height:27px;} +footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;} +.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;} +.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;} +.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;} +.btn:focus{outline:1px dotted #666;} +.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.btn.small{padding:7px 9px 7px;font-size:11px;} +:root .alert-message,:root .btn{border-radius:0 \0;} +button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;} +.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);-khtml-opacity:0.2;-moz-opacity:0.2;opacity:0.2;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;} +.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{*margin-top:3px;} +.alert-message h5{line-height:18px;} +.alert-message p{margin-bottom:0;} +.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;} +.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);} +.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;} +.alert-message.block-message ul{margin-bottom:0;} +.alert-message.block-message li{color:#404040;} +.alert-message.block-message .alert-actions{margin-top:5px;} +.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;} +.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;} +.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;} +.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination li{display:inline;} +.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;} +.pagination a:hover,.pagination .active a{background-color:#c7eefe;} +.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;} +.pagination .next a{border:0;} +.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} +.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -250px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;} +.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:50%;} +.modal-header{border-bottom:1px solid #eee;padding:5px 15px;} +.modal-body{padding:15px;} +.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;*display:inline;} +.modal-footer:after{clear:both;} +.modal-footer .btn{float:right;margin-left:5px;} +.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} +.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.twipsy-arrow{position:absolute;width:0;height:0;} +.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.popover .arrow{position:absolute;width:0;height:0;} +.popover .inner{background-color:#000000;background-color:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} +.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;} +.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;} +.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;} +.label{padding:1px 3px 2px;background-color:#bfbfbf;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;} +.label.warning{background-color:#f89406;} +.label.success{background-color:#46a546;} +.label.notice{background-color:#62cffc;} +.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;*display:inline;} +.media-grid:after{clear:both;} +.media-grid li{display:inline;} +.media-grid a{float:left;padding:4px;margin:0 0 20px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;} +.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} diff --git a/samples-and-tests/testapp/app/controllers/Application.java b/samples-and-tests/testapp/app/controllers/Application.java new file mode 100644 index 000000000..2f123d174 --- /dev/null +++ b/samples-and-tests/testapp/app/controllers/Application.java @@ -0,0 +1,12 @@ +package controllers; + +import controllers.securesocial.SecureSocial; +import play.mvc.Controller; +import play.mvc.With; + +@With( SecureSocial.class ) +public class Application extends Controller { + public static void index() { + render(); + } +} \ No newline at end of file diff --git a/samples-and-tests/testapp/app/views/Application/index.html b/samples-and-tests/testapp/app/views/Application/index.html new file mode 100644 index 000000000..1ed217d9e --- /dev/null +++ b/samples-and-tests/testapp/app/views/Application/index.html @@ -0,0 +1,35 @@ +#{extends 'main.html' /} +#{set title:'Home' /} + +
+ + +
+

User details

+ User id: ${user.id.id}
+ Logged in from: ${user.id.provider}
+ Email: ${user.email}
+ Authentication method: ${user.authMethod} + + #{if user.authMethod == securesocial.provider.AuthenticationMethod.OAUTH1 || + user.authMethod == securesocial.provider.AuthenticationMethod.OPENID_OAUTH_HYBRID} +

OAuth 1

+ Token = ${user.token}
+ Secret = ${user.secret} + #{/if} + + #{if user.authMethod == securesocial.provider.AuthenticationMethod.OAUTH2} +

OAuth 2

+ Access token = ${user.accessToken} + #{/if} +
+ +
+ Logout +
+
+ + + diff --git a/samples-and-tests/testapp/app/views/errors/404.html b/samples-and-tests/testapp/app/views/errors/404.html new file mode 100644 index 000000000..eb0b00e54 --- /dev/null +++ b/samples-and-tests/testapp/app/views/errors/404.html @@ -0,0 +1,19 @@ + + + + + Not found + + + + #{if play.mode.name() == 'DEV'} + #{404 result /} + #{/if} + #{else} +

Not found

+

+ ${result.message} +

+ #{/else} + + diff --git a/samples-and-tests/testapp/app/views/errors/500.html b/samples-and-tests/testapp/app/views/errors/500.html new file mode 100644 index 000000000..a898cb77e --- /dev/null +++ b/samples-and-tests/testapp/app/views/errors/500.html @@ -0,0 +1,21 @@ + + + + + Application error + + + + #{if play.mode.name() == 'DEV'} + #{500 exception /} + #{/if} + #{else} +

Oops, an error occured

+ #{if exception instanceof play.exceptions.PlayException} +

+ This exception has been logged with id ${exception.id}. +

+ #{/if} + #{/else} + + diff --git a/samples-and-tests/testapp/app/views/main.html b/samples-and-tests/testapp/app/views/main.html new file mode 100644 index 000000000..bdb69f0d9 --- /dev/null +++ b/samples-and-tests/testapp/app/views/main.html @@ -0,0 +1,31 @@ + + + + + #{get 'title' /} + + + + #{get 'moreStyles' /} + + #{get 'moreScripts' /} + + +
+ +
+
+
+
+ #{doLayout /} +
+
+
+ + + diff --git a/samples-and-tests/testapp/conf/application.conf b/samples-and-tests/testapp/conf/application.conf new file mode 100644 index 000000000..03dc28d02 --- /dev/null +++ b/samples-and-tests/testapp/conf/application.conf @@ -0,0 +1,296 @@ +# This is the main configuration file for the application. +# ~~~~~ +application.name=testapp + +# Application mode +# ~~~~~ +# Set to dev to enable instant reloading and other development help. +# Otherwise set to prod. +application.mode=dev +%prod.application.mode=prod + +# Secret key +# ~~~~~ +# The secret key is used to secure cryptographics functions +# If you deploy your application to several instances be sure to use the same key ! +application.secret=koBwgGImJcd4zZ00OWaed9QCS2q8y1f5KBe7JQiJNtBzNPpQIe0iq5xCKvYw1o0j + +# i18n +# ~~~~~ +# Define locales used by your application. +# You can then place localized messages in conf/messages.{locale} files +# application.langs=fr,en,ja + +# Date format +# ~~~~~ +date.format=yyyy-MM-dd +# date.format.fr=dd/MM/yyyy + +# Server configuration +# ~~~~~ +# If you need to change the HTTP port, uncomment this (default is set to 9000) +# http.port=9000 +# +# By default the server listen for HTTP on the wilcard address. +# You can restrict this. +# http.address=127.0.0.1 +# +# Use this if you don't host your Play application at the root of the domain +# you're serving it from. This parameter has no effect when deployed as a +# war, because the path will be handled by the application server. +# http.path=/ + +# Session configuration +# ~~~~~~~~~~~~~~~~~~~~~~ +# By default, session will be written to the transient PLAY_SESSION cookie. +# The cookies are not secured by default, only set it to true +# if you're serving your pages through https. +# application.session.cookie=PLAY +# application.session.maxAge=1h +# application.session.secure=false + +# Session/Cookie sharing between subdomain +# ~~~~~~~~~~~~~~~~~~~~~~ +# By default a cookie is only valid for a specific domain. By setting +# application.defaultCookieDomain to '.example.com', the cookies +# will be valid for all domains ending with '.example.com', ie: +# foo.example.com and bar.example.com +# application.defaultCookieDomain=.example.com + +# JVM configuration +# ~~~~~ +# Define which port is used by JPDA when application is in debug mode (default is set to 8000) +# jpda.port=8000 +# +# Java source level => 1.5, 1.6 or 1.7 (experimental) +# java.source=1.5 + +# Log level +# ~~~~~ +# Specify log level for your application. +# If you want a very customized log, create a log4j.properties file in the conf directory +# application.log=INFO +# + +# More logging configuration +# application.log.path=/log4j.properties +# application.log.system.out=off + +# Database configuration +# ~~~~~ +# Enable a database engine if needed. +# +# To quickly set up a development database, use either: +# - mem : for a transient in memory database (H2 in memory) +# - fs : for a simple file written database (H2 file stored) +# db=mem +# +# To connect to a local MySQL5 database, use: +# db=mysql://user:pwd@host/database +# +# To connect to a local PostgreSQL9 database, use: +# db=postgres://user:pwd@host/database +# +# If you need a full JDBC configuration use the following : +# db.url=jdbc:postgresql:database_name +# db.driver=org.postgresql.Driver +# db.user=root +# db.pass=secret +# +# Connections pool configuration : +# db.pool.timeout=1000 +# db.pool.maxSize=30 +# db.pool.minSize=10 +# +# If you want to reuse an existing Datasource from your application server, use: +# db=java:/comp/env/jdbc/myDatasource +# +# When using an existing Datasource, it's sometimes needed to destroy it when +# the application is stopped. Depending on the datasource, you can define a +# generic "destroy" method : +# db.destroyMethod=close + +# JPA Configuration (Hibernate) +# ~~~~~ +# +# Specify the custom JPA dialect to use here (default to guess): +# jpa.dialect=org.hibernate.dialect.PostgreSQLDialect +# +# Specify the ddl generation pattern to use. Set to none to disable it +# (default to update in DEV mode, and none in PROD mode): +# jpa.ddl=update +# +# Debug SQL statements (logged using DEBUG level): +# jpa.debugSQL=true +# +# You can even specify additional hibernate properties here: +# hibernate.use_sql_comments=true +# ... +# +# Store path for Blob content +attachments.path=data/attachments + +# Memcached configuration +# ~~~~~ +# Enable memcached if needed. Otherwise a local cache is used. +# memcached=enabled +# +# Specify memcached host (default to 127.0.0.1:11211) +# memcached.host=127.0.0.1:11211 +# +# Or you can specify multiple host to build a distributed cache +# memcached.1.host=127.0.0.1:11211 +# memcached.2.host=127.0.0.1:11212 +# +# Use plain SASL to authenticate for memcached +# memcached.user= +# memcached.password= + +# HTTP Response headers control for static files +# ~~~~~ +# Set the default max-age, telling the user's browser how long it should cache the page. +# Default is 3600 (one hour). Set it to 0 to send no-cache. +# This is only read in prod mode, in dev mode the cache is disabled. +# http.cacheControl=3600 + +# If enabled, Play will generate entity tags automatically and send a 304 when needed. +# Default is true, set it to false to deactivate use of entity tags. +# http.useETag=true + +# Custom mime types +# mimetype.xpi=application/x-xpinstall + +# WS configuration +# ~~~~~ +# Default engine is Async Http Client, uncomment to use +# the JDK's internal implementation +# webservice = urlfetch +# If you need to set proxy params for WS requests +# http.proxyHost = localhost +# http.proxyPort = 3128 +# http.proxyUser = jojo +# http.proxyPassword = jojo + + +# Mail configuration +# ~~~~~ +# Default is to use a mock Mailer +mail.smtp=mock + +# Or, specify mail host configuration +# mail.smtp.host=127.0.0.1 +# mail.smtp.user=admin +# mail.smtp.pass= +# mail.smtp.channel=ssl + +# Url-resolving in Jobs +# ~~~~~~ +# When rendering templates with reverse-url-resoling (@@{..}) in Jobs (which do not have an inbound Http.Request), +# ie if sending a HtmlMail, Play need to know which url your users use when accessing your app. +# %test.application.baseUrl=http://localhost:9000/ +# %prod.application.baseUrl=http://www.yourdomain.com/ + +# Jobs executor +# ~~~~~~ +# Size of the Jobs pool +# play.jobs.pool=10 + +# Execution pool +# ~~~~~ +# Default to 1 thread in DEV mode or (nb processors + 1) threads in PROD mode. +# Try to keep a low as possible. 1 thread will serialize all requests (very useful for debugging purpose) +# play.pool=3 + +# Open file from errors pages +# ~~~~~ +# If your text editor supports opening files by URL, Play! will +# dynamically link error pages to files +# +# Example, for textmate: +# play.editor=txmt://open?url=file://%s&line=%s + +# Testing. Set up a custom configuration for test mode +# ~~~~~ +#%test.module.cobertura=${play.path}/modules/cobertura +%test.application.mode=dev +%test.db.url=jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE=0 +%test.jpa.ddl=create +%test.mail.smtp=mock + + +################################################################################################ +# +# Secure Social Module Settings +# +################################################################################################ + +# +# Mailer +# +securesocial.mailer.subject=Activate your account +securesocial.mailer.from=you@at.your.company.com + +# +# The providers your app wants to use and the order in which they appear on the login page. +# +# Note that the userpass provider always appears below the icon row. If you need another layout +# you'll have to change the template. +# +securesocial.providers=twitter,facebook,google,yahoo,foursquare,linkedin,wordpress,myopenid,userpass + +# +# Twitter +# +securesocial.twitter.requestTokenURL=https://twitter.com/oauth/request_token +securesocial.twitter.accessTokenURL=https://twitter.com/oauth/access_token +securesocial.twitter.authorizationURL=https://twitter.com/oauth/authenticate +securesocial.twitter.consumerKey=your_consumer_key +securesocial.twitter.consumerSecret=your_consumer_secret + + + +# +# Facebook +# +securesocial.facebook.authorizationURL=https://graph.facebook.com/oauth/authorize +securesocial.facebook.accessTokenURL=https://graph.facebook.com/oauth/access_token +securesocial.facebook.clientid=your_client_id +securesocial.facebook.secret=your_client_secret +# this scope is the minimum SecureSocial requires. You can add more if required by your app. +securesocial.facebook.scope=email + +# +# Google +# +securesocial.google.requestTokenURL=https://www.google.com/accounts/OAuthGetRequestToken +securesocial.google.accessTokenURL=https://www.google.com/accounts/OAuthGetAccessToken +securesocial.google.authorizationURL=https://www.google.com/accounts/OAuthAuthorizeToken +securesocial.google.scope=http://www-opensocial.googleusercontent.com/api/people +securesocial.google.consumerKey=your_consumer_key +securesocial.google.consumerSecret=your_consumer_secret + +# +# Yahoo +# +securesocial.yahoo.requestTokenURL=https://api.login.yahoo.com/oauth/v2/get_request_token +securesocial.yahoo.accessTokenURL=https://api.login.yahoo.com/oauth/v2/get_token +securesocial.yahoo.authorizationURL=https://api.login.yahoo.com/oauth/v2/request_auth +securesocial.yahoo.consumerKey=your_consumer_key +securesocial.yahoo.consumerSecret=your_consumer_secret + +# +# LinkedIn +# +securesocial.linkedin.requestTokenURL=https://api.linkedin.com/uas/oauth/requestToken +securesocial.linkedin.accessTokenURL=https://api.linkedin.com/uas/oauth/accessToken +securesocial.linkedin.authorizationURL=https://api.linkedin.com/uas/oauth/authenticate +securesocial.linkedin.consumerKey=your_consumer_key +securesocial.linkedin.consumerSecret=your_consumer_secret + +# +# Foursquare +# +securesocial.foursquare.authorizationURL=https://foursquare.com/oauth2/authenticate?response_type=code +securesocial.foursquare.accessTokenURL=https://foursquare.com/oauth2/access_token?grant_type=authorization_code +securesocial.foursquare.clientid=your_consumer_key +securesocial.foursquare.secret=your_consumer_secret \ No newline at end of file diff --git a/samples-and-tests/testapp/conf/dependencies.yml b/samples-and-tests/testapp/conf/dependencies.yml new file mode 100644 index 000000000..99f27d844 --- /dev/null +++ b/samples-and-tests/testapp/conf/dependencies.yml @@ -0,0 +1,12 @@ +# Application dependencies + +require: + - play + - securesocial -> securesocial + +repositories: + - My modules: + type: local + artifact: /../../securesocial/ + contains: + - securesocial diff --git a/samples-and-tests/testapp/conf/messages b/samples-and-tests/testapp/conf/messages new file mode 100644 index 000000000..b51f29459 --- /dev/null +++ b/samples-and-tests/testapp/conf/messages @@ -0,0 +1,3 @@ +# You can specialize this file for each language. +# For example, for French create a messages.fr file +# diff --git a/samples-and-tests/testapp/conf/routes b/samples-and-tests/testapp/conf/routes new file mode 100644 index 000000000..e3a243aa3 --- /dev/null +++ b/samples-and-tests/testapp/conf/routes @@ -0,0 +1,18 @@ +# Routes +# This file defines all application routes (Higher priority routes first) +# ~~~~ + + +* /auth module:securesocial + +# Home page +GET / Application.index + +# Ignore favicon requests +GET /favicon.ico 404 + +# Map static resources from the /app/public folder to the /public path +GET /public/ staticDir:public + +# Catch all +* /{controller}/{action} {controller}.{action} diff --git a/samples-and-tests/testapp/modules/securesocial b/samples-and-tests/testapp/modules/securesocial new file mode 100644 index 000000000..046645ef2 --- /dev/null +++ b/samples-and-tests/testapp/modules/securesocial @@ -0,0 +1 @@ +/Users/jorge/projects/securesocial diff --git a/samples-and-tests/testapp/public/images/favicon.png b/samples-and-tests/testapp/public/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d92d2ae47434d9a61c90bc205e099b673b9dd5 GIT binary patch literal 687 zcmV;g0#N;lP)ezT{T_ZJ?}AL z5NC{NW(ESID=>(O3&Eg8 zmA9J&6c`h4_f6L;=bU>_H8aNG`kfvCj9zomNt)?O;rzWqZs0LEt%1WB218%1fo9uB zsW^yhBR7C(mqN%GEK9&msg0~ zWY?#bf4q8G-~2KttQZ($odJvy&_-~f?9*ThK@fwR$U^1)p*8=_+^3BXx0$i1BC8XC zr21u6D5nVK&^!dOAw&|1E;qC3uFNj3*Jj#&%Oje@0D-nhfmM*o%^5f}-pxQ07(95H z3|LoV>V19w#rLgmRmtVy9!T3M3FUE3><0T8&b3yEsWcLW`0(=1+qsqc(k(ymBLK0h zK!6(6$7MX~M`-QA2$wk7n(7hhkJ}4Rwi-Vd(_ZFX1Yk7TXuB0IJYpo@kLb2G8m)E{ z`9v=!hi}fOytKckfN^C@6+Z*+MVI9-W_p@_3yyR#UYc0FTpD}i#k>c!wYCS)4v@E$ zchZCo=zV@)`v^$;V18ixdjFMY#q^2$wEX%{f(XD8POnsn$bpbClpC@hPxjzyO>pY|*pF3UU2tYcCN?rUk{Sskej70Mmu9vPwMYhO1m{AxAt(zqDT|0jP7FaX=6 V`?~}E4H^Id002ovPDHLkV1hC)G==~G literal 0 HcmV?d00001 diff --git a/samples-and-tests/testapp/public/javascripts/jquery-1.5.2.min.js b/samples-and-tests/testapp/public/javascripts/jquery-1.5.2.min.js new file mode 100644 index 000000000..f78f96a12 --- /dev/null +++ b/samples-and-tests/testapp/public/javascripts/jquery-1.5.2.min.js @@ -0,0 +1,16 @@ +/*! + * jQuery JavaScript Library v1.5.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Mar 31 15:28:23 2011 -0400 + */ +(function(a,b){function ci(a){return d.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cf(a){if(!b_[a]){var b=d("<"+a+">").appendTo("body"),c=b.css("display");b.remove();if(c==="none"||c==="")c="block";b_[a]=c}return b_[a]}function ce(a,b){var c={};d.each(cd.concat.apply([],cd.slice(0,b)),function(){c[this]=a});return c}function b$(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function bZ(){try{return new a.XMLHttpRequest}catch(b){}}function bY(){d(a).unload(function(){for(var a in bW)bW[a](0,1)})}function bS(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var e=a.dataTypes,f={},g,h,i=e.length,j,k=e[0],l,m,n,o,p;for(g=1;g=0===c})}function P(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function H(a,b){return(a&&a!=="*"?a+".":"")+b.replace(t,"`").replace(u,"&")}function G(a){var b,c,e,f,g,h,i,j,k,l,m,n,o,p=[],q=[],s=d._data(this,"events");if(a.liveFired!==this&&s&&s.live&&!a.target.disabled&&(!a.button||a.type!=="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var t=s.live.slice(0);for(i=0;ic)break;a.currentTarget=f.elem,a.data=f.handleObj.data,a.handleObj=f.handleObj,o=f.handleObj.origHandler.apply(f.elem,arguments);if(o===!1||a.isPropagationStopped()){c=f.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function E(a,c,e){var f=d.extend({},e[0]);f.type=a,f.originalEvent={},f.liveFired=b,d.event.handle.call(c,f),f.isDefaultPrevented()&&e[0].preventDefault()}function y(){return!0}function x(){return!1}function i(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function h(a,c,e){if(e===b&&a.nodeType===1){e=a.getAttribute("data-"+c);if(typeof e==="string"){try{e=e==="true"?!0:e==="false"?!1:e==="null"?null:d.isNaN(e)?g.test(e)?d.parseJSON(e):e:parseFloat(e)}catch(f){}d.data(a,c,e)}else e=b}return e}var c=a.document,d=function(){function G(){if(!d.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(G,1);return}d.ready()}}var d=function(a,b){return new d.fn.init(a,b,g)},e=a.jQuery,f=a.$,g,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,i=/\S/,j=/^\s+/,k=/\s+$/,l=/\d/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=navigator.userAgent,w,x,y,z=Object.prototype.toString,A=Object.prototype.hasOwnProperty,B=Array.prototype.push,C=Array.prototype.slice,D=String.prototype.trim,E=Array.prototype.indexOf,F={};d.fn=d.prototype={constructor:d,init:function(a,e,f){var g,i,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!e&&c.body){this.context=c,this[0]=c.body,this.selector="body",this.length=1;return this}if(typeof a==="string"){g=h.exec(a);if(!g||!g[1]&&e)return!e||e.jquery?(e||f).find(a):this.constructor(e).find(a);if(g[1]){e=e instanceof d?e[0]:e,k=e?e.ownerDocument||e:c,j=m.exec(a),j?d.isPlainObject(e)?(a=[c.createElement(j[1])],d.fn.attr.call(a,e,!0)):a=[k.createElement(j[1])]:(j=d.buildFragment([g[1]],[k]),a=(j.cacheable?d.clone(j.fragment):j.fragment).childNodes);return d.merge(this,a)}i=c.getElementById(g[2]);if(i&&i.parentNode){if(i.id!==g[2])return f.find(a);this.length=1,this[0]=i}this.context=c,this.selector=a;return this}if(d.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return d.makeArray(a,this)},selector:"",jquery:"1.5.2",length:0,size:function(){return this.length},toArray:function(){return C.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var e=this.constructor();d.isArray(a)?B.apply(e,a):d.merge(e,a),e.prevObject=this,e.context=this.context,b==="find"?e.selector=this.selector+(this.selector?" ":"")+c:b&&(e.selector=this.selector+"."+b+"("+c+")");return e},each:function(a,b){return d.each(this,a,b)},ready:function(a){d.bindReady(),x.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(C.apply(this,arguments),"slice",C.call(arguments).join(","))},map:function(a){return this.pushStack(d.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:B,sort:[].sort,splice:[].splice},d.fn.init.prototype=d.fn,d.extend=d.fn.extend=function(){var a,c,e,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i==="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!=="object"&&!d.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;x.resolveWith(c,[d]),d.fn.trigger&&d(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!x){x=d._Deferred();if(c.readyState==="complete")return setTimeout(d.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",y,!1),a.addEventListener("load",d.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",y),a.attachEvent("onload",d.ready);var b=!1;try{b=a.frameElement==null}catch(e){}c.documentElement.doScroll&&b&&G()}}},isFunction:function(a){return d.type(a)==="function"},isArray:Array.isArray||function(a){return d.type(a)==="array"},isWindow:function(a){return a&&typeof a==="object"&&"setInterval"in a},isNaN:function(a){return a==null||!l.test(a)||isNaN(a)},type:function(a){return a==null?String(a):F[z.call(a)]||"object"},isPlainObject:function(a){if(!a||d.type(a)!=="object"||a.nodeType||d.isWindow(a))return!1;if(a.constructor&&!A.call(a,"constructor")&&!A.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a){}return c===b||A.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!=="string"||!b)return null;b=d.trim(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return a.JSON&&a.JSON.parse?a.JSON.parse(b):(new Function("return "+b))();d.error("Invalid JSON: "+b)},parseXML:function(b,c,e){a.DOMParser?(e=new DOMParser,c=e.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),e=c.documentElement,(!e||!e.nodeName||e.nodeName==="parsererror")&&d.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(a){if(a&&i.test(a)){var b=c.head||c.getElementsByTagName("head")[0]||c.documentElement,e=c.createElement("script");d.support.scriptEval()?e.appendChild(c.createTextNode(a)):e.text=a,b.insertBefore(e,b.firstChild),b.removeChild(e)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,e){var f,g=0,h=a.length,i=h===b||d.isFunction(a);if(e){if(i){for(f in a)if(c.apply(a[f],e)===!1)break}else for(;g1?f.call(arguments,0):c,--g||h.resolveWith(h,f.call(b,0))}}var b=arguments,c=0,e=b.length,g=e,h=e<=1&&a&&d.isFunction(a.promise)?a:d.Deferred();if(e>1){for(;c
a";var e=b.getElementsByTagName("*"),f=b.getElementsByTagName("a")[0],g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=b.getElementsByTagName("input")[0];if(e&&e.length&&f){d.support={leadingWhitespace:b.firstChild.nodeType===3,tbody:!b.getElementsByTagName("tbody").length,htmlSerialize:!!b.getElementsByTagName("link").length,style:/red/.test(f.getAttribute("style")),hrefNormalized:f.getAttribute("href")==="/a",opacity:/^0.55$/.test(f.style.opacity),cssFloat:!!f.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,deleteExpando:!0,optDisabled:!1,checkClone:!1,noCloneEvent:!0,noCloneChecked:!0,boxModel:null,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableHiddenOffsets:!0,reliableMarginRight:!0},i.checked=!0,d.support.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,d.support.optDisabled=!h.disabled;var j=null;d.support.scriptEval=function(){if(j===null){var b=c.documentElement,e=c.createElement("script"),f="script"+d.now();try{e.appendChild(c.createTextNode("window."+f+"=1;"))}catch(g){}b.insertBefore(e,b.firstChild),a[f]?(j=!0,delete a[f]):j=!1,b.removeChild(e)}return j};try{delete b.test}catch(k){d.support.deleteExpando=!1}!b.addEventListener&&b.attachEvent&&b.fireEvent&&(b.attachEvent("onclick",function l(){d.support.noCloneEvent=!1,b.detachEvent("onclick",l)}),b.cloneNode(!0).fireEvent("onclick")),b=c.createElement("div"),b.innerHTML="";var m=c.createDocumentFragment();m.appendChild(b.firstChild),d.support.checkClone=m.cloneNode(!0).cloneNode(!0).lastChild.checked,d(function(){var a=c.createElement("div"),b=c.getElementsByTagName("body")[0];if(b){a.style.width=a.style.paddingLeft="1px",b.appendChild(a),d.boxModel=d.support.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,d.support.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",d.support.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
";var e=a.getElementsByTagName("td");d.support.reliableHiddenOffsets=e[0].offsetHeight===0,e[0].style.display="",e[1].style.display="none",d.support.reliableHiddenOffsets=d.support.reliableHiddenOffsets&&e[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(a.style.width="1px",a.style.marginRight="0",d.support.reliableMarginRight=(parseInt(c.defaultView.getComputedStyle(a,null).marginRight,10)||0)===0),b.removeChild(a).style.display="none",a=e=null}});var n=function(a){var b=c.createElement("div");a="on"+a;if(!b.attachEvent)return!0;var d=a in b;d||(b.setAttribute(a,"return;"),d=typeof b[a]==="function");return d};d.support.submitBubbles=n("submit"),d.support.changeBubbles=n("change"),b=e=f=null}}();var g=/^(?:\{.*\}|\[.*\])$/;d.extend({cache:{},uuid:0,expando:"jQuery"+(d.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?d.cache[a[d.expando]]:a[d.expando];return!!a&&!i(a)},data:function(a,c,e,f){if(d.acceptData(a)){var g=d.expando,h=typeof c==="string",i,j=a.nodeType,k=j?d.cache:a,l=j?a[d.expando]:a[d.expando]&&d.expando;if((!l||f&&l&&!k[l][g])&&h&&e===b)return;l||(j?a[d.expando]=l=++d.uuid:l=d.expando),k[l]||(k[l]={},j||(k[l].toJSON=d.noop));if(typeof c==="object"||typeof c==="function")f?k[l][g]=d.extend(k[l][g],c):k[l]=d.extend(k[l],c);i=k[l],f&&(i[g]||(i[g]={}),i=i[g]),e!==b&&(i[c]=e);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,e){if(d.acceptData(b)){var f=d.expando,g=b.nodeType,h=g?d.cache:b,j=g?b[d.expando]:d.expando;if(!h[j])return;if(c){var k=e?h[j][f]:h[j];if(k){delete k[c];if(!i(k))return}}if(e){delete h[j][f];if(!i(h[j]))return}var l=h[j][f];d.support.deleteExpando||h!=a?delete h[j]:h[j]=null,l?(h[j]={},g||(h[j].toJSON=d.noop),h[j][f]=l):g&&(d.support.deleteExpando?delete b[d.expando]:b.removeAttribute?b.removeAttribute(d.expando):b[d.expando]=null)}},_data:function(a,b,c){return d.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=d.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),d.fn.extend({data:function(a,c){var e=null;if(typeof a==="undefined"){if(this.length){e=d.data(this[0]);if(this[0].nodeType===1){var f=this[0].attributes,g;for(var i=0,j=f.length;i-1)return!0;return!1},val:function(a){if(!arguments.length){var c=this[0];if(c){if(d.nodeName(c,"option")){var e=c.attributes.value;return!e||e.specified?c.value:c.text}if(d.nodeName(c,"select")){var f=c.selectedIndex,g=[],h=c.options,i=c.type==="select-one";if(f<0)return null;for(var j=i?f:0,k=i?f+1:h.length;j=0;else if(d.nodeName(this,"select")){var f=d.makeArray(e);d("option",this).each(function(){this.selected=d.inArray(d(this).val(),f)>=0}),f.length||(this.selectedIndex=-1)}else this.value=e}})}}),d.extend({attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,e,f){if(!a||a.nodeType===3||a.nodeType===8||a.nodeType===2)return b;if(f&&c in d.attrFn)return d(a)[c](e);var g=a.nodeType!==1||!d.isXMLDoc(a),h=e!==b;c=g&&d.props[c]||c;if(a.nodeType===1){var i=m.test(c);if(c==="selected"&&!d.support.optSelected){var j=a.parentNode;j&&(j.selectedIndex,j.parentNode&&j.parentNode.selectedIndex)}if((c in a||a[c]!==b)&&g&&!i){h&&(c==="type"&&n.test(a.nodeName)&&a.parentNode&&d.error("type property can't be changed"),e===null?a.nodeType===1&&a.removeAttribute(c):a[c]=e);if(d.nodeName(a,"form")&&a.getAttributeNode(c))return a.getAttributeNode(c).nodeValue;if(c==="tabIndex"){var k=a.getAttributeNode("tabIndex");return k&&k.specified?k.value:o.test(a.nodeName)||p.test(a.nodeName)&&a.href?0:b}return a[c]}if(!d.support.style&&g&&c==="style"){h&&(a.style.cssText=""+e);return a.style.cssText}h&&a.setAttribute(c,""+e);if(!a.attributes[c]&&(a.hasAttribute&&!a.hasAttribute(c)))return b;var l=!d.support.hrefNormalized&&g&&i?a.getAttribute(c,2):a.getAttribute(c);return l===null?b:l}h&&(a[c]=e);return a[c]}});var r=/\.(.*)$/,s=/^(?:textarea|input|select)$/i,t=/\./g,u=/ /g,v=/[^\w\s.|`]/g,w=function(a){return a.replace(v,"\\$&")};d.event={add:function(c,e,f,g){if(c.nodeType!==3&&c.nodeType!==8){try{d.isWindow(c)&&(c!==a&&!c.frameElement)&&(c=a)}catch(h){}if(f===!1)f=x;else if(!f)return;var i,j;f.handler&&(i=f,f=i.handler),f.guid||(f.guid=d.guid++);var k=d._data(c);if(!k)return;var l=k.events,m=k.handle;l||(k.events=l={}),m||(k.handle=m=function(a){return typeof d!=="undefined"&&d.event.triggered!==a.type?d.event.handle.apply(m.elem,arguments):b}),m.elem=c,e=e.split(" ");var n,o=0,p;while(n=e[o++]){j=i?d.extend({},i):{handler:f,data:g},n.indexOf(".")>-1?(p=n.split("."),n=p.shift(),j.namespace=p.slice(0).sort().join(".")):(p=[],j.namespace=""),j.type=n,j.guid||(j.guid=f.guid);var q=l[n],r=d.event.special[n]||{};if(!q){q=l[n]=[];if(!r.setup||r.setup.call(c,g,p,m)===!1)c.addEventListener?c.addEventListener(n,m,!1):c.attachEvent&&c.attachEvent("on"+n,m)}r.add&&(r.add.call(c,j),j.handler.guid||(j.handler.guid=f.guid)),q.push(j),d.event.global[n]=!0}c=null}},global:{},remove:function(a,c,e,f){if(a.nodeType!==3&&a.nodeType!==8){e===!1&&(e=x);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=d.hasData(a)&&d._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(e=c.handler,c=c.type);if(!c||typeof c==="string"&&c.charAt(0)==="."){c=c||"";for(h in t)d.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+d.map(m.slice(0).sort(),w).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!e){for(j=0;j=0&&(a.type=f=f.slice(0,-1),a.exclusive=!0),e||(a.stopPropagation(),d.event.global[f]&&d.each(d.cache,function(){var b=d.expando,e=this[b];e&&e.events&&e.events[f]&&d.event.trigger(a,c,e.handle.elem)}));if(!e||e.nodeType===3||e.nodeType===8)return b;a.result=b,a.target=e,c=d.makeArray(c),c.unshift(a)}a.currentTarget=e;var h=d._data(e,"handle");h&&h.apply(e,c);var i=e.parentNode||e.ownerDocument;try{e&&e.nodeName&&d.noData[e.nodeName.toLowerCase()]||e["on"+f]&&e["on"+f].apply(e,c)===!1&&(a.result=!1,a.preventDefault())}catch(j){}if(!a.isPropagationStopped()&&i)d.event.trigger(a,c,i,!0);else if(!a.isDefaultPrevented()){var k,l=a.target,m=f.replace(r,""),n=d.nodeName(l,"a")&&m==="click",o=d.event.special[m]||{};if((!o._default||o._default.call(e,a)===!1)&&!n&&!(l&&l.nodeName&&d.noData[l.nodeName.toLowerCase()])){try{l[m]&&(k=l["on"+m],k&&(l["on"+m]=null),d.event.triggered=a.type,l[m]())}catch(p){}k&&(l["on"+m]=k),d.event.triggered=b}}},handle:function(c){var e,f,g,h,i,j=[],k=d.makeArray(arguments);c=k[0]=d.event.fix(c||a.event),c.currentTarget=this,e=c.type.indexOf(".")<0&&!c.exclusive,e||(g=c.type.split("."),c.type=g.shift(),j=g.slice(0).sort(),h=new RegExp("(^|\\.)"+j.join("\\.(?:.*\\.)?")+"(\\.|$)")),c.namespace=c.namespace||j.join("."),i=d._data(this,"events"),f=(i||{})[c.type];if(i&&f){f=f.slice(0);for(var l=0,m=f.length;l-1?d.map(a.options,function(a){return a.selected}).join("-"):"":a.nodeName.toLowerCase()==="select"&&(c=a.selectedIndex);return c},D=function D(a){var c=a.target,e,f;if(s.test(c.nodeName)&&!c.readOnly){e=d._data(c,"_change_data"),f=C(c),(a.type!=="focusout"||c.type!=="radio")&&d._data(c,"_change_data",f);if(e===b||f===e)return;if(e!=null||f)a.type="change",a.liveFired=b,d.event.trigger(a,arguments[1],c)}};d.event.special.change={filters:{focusout:D,beforedeactivate:D,click:function(a){var b=a.target,c=b.type;(c==="radio"||c==="checkbox"||b.nodeName.toLowerCase()==="select")&&D.call(this,a)},keydown:function(a){var b=a.target,c=b.type;(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&D.call(this,a)},beforeactivate:function(a){var b=a.target;d._data(b,"_change_data",C(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in B)d.event.add(this,c+".specialChange",B[c]);return s.test(this.nodeName)},teardown:function(a){d.event.remove(this,".specialChange");return s.test(this.nodeName)}},B=d.event.special.change.filters,B.focus=B.beforeactivate}c.addEventListener&&d.each({focus:"focusin",blur:"focusout"},function(a,b){function f(a){var c=d.event.fix(a);c.type=b,c.originalEvent={},d.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var e=0;d.event.special[b]={setup:function(){e++===0&&c.addEventListener(a,f,!0)},teardown:function(){--e===0&&c.removeEventListener(a,f,!0)}}}),d.each(["bind","one"],function(a,c){d.fn[c]=function(a,e,f){if(typeof a==="object"){for(var g in a)this[c](g,e,a[g],f);return this}if(d.isFunction(e)||e===!1)f=e,e=b;var h=c==="one"?d.proxy(f,function(a){d(this).unbind(a,h);return f.apply(this,arguments)}):f;if(a==="unload"&&c!=="one")this.one(a,e,f);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},d.attrFn&&(d.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,e,g){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!=="string")return e;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(f.call(n)==="[object Array]")if(u)if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&e.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&e.push(j[t]);else e.push.apply(e,n);else p(n,e);o&&(k(o,h,e,g),k.uniqueSort(e));return e};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b==="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return"text"===c&&(b===c||b===null)},radio:function(a){return"radio"===a.type},checkbox:function(a){return"checkbox"===a.type},file:function(a){return"file"===a.type},password:function(a){return"password"===a.type},submit:function(a){return"submit"===a.type},image:function(a){return"image"===a.type},reset:function(a){return"reset"===a.type},button:function(a){return"button"===a.type||a.nodeName.toLowerCase()==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(f.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length==="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!=="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!=="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!=="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!=="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!=="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(var g=c;g0},closest:function(a,b){var c=[],e,f,g=this[0];if(d.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(e=0,f=a.length;e-1:d(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=N.test(a)?d(a,b||this.context):null;for(e=0,f=this.length;e-1:d.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b)break}}c=c.length>1?d.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a==="string")return d.inArray(this[0],a?d(a):this.parent().children());return d.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a==="string"?d(a,b):d.makeArray(a),e=d.merge(this.get(),c);return this.pushStack(P(c[0])||P(e[0])?e:d.unique(e))},andSelf:function(){return this.add(this.prevObject)}}),d.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return d.dir(a,"parentNode")},parentsUntil:function(a,b,c){return d.dir(a,"parentNode",c)},next:function(a){return d.nth(a,2,"nextSibling")},prev:function(a){return d.nth(a,2,"previousSibling")},nextAll:function(a){return d.dir(a,"nextSibling")},prevAll:function(a){return d.dir(a,"previousSibling")},nextUntil:function(a,b,c){return d.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return d.dir(a,"previousSibling",c)},siblings:function(a){return d.sibling(a.parentNode.firstChild,a)},children:function(a){return d.sibling(a.firstChild)},contents:function(a){return d.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:d.makeArray(a.childNodes)}},function(a,b){d.fn[a]=function(c,e){var f=d.map(this,b,c),g=M.call(arguments);I.test(a)||(e=c),e&&typeof e==="string"&&(f=d.filter(e,f)),f=this.length>1&&!O[a]?d.unique(f):f,(this.length>1||K.test(e))&&J.test(a)&&(f=f.reverse());return this.pushStack(f,a,g.join(","))}}),d.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?d.find.matchesSelector(b[0],a)?[b[0]]:[]:d.find.matches(a,b)},dir:function(a,c,e){var f=[],g=a[c];while(g&&g.nodeType!==9&&(e===b||g.nodeType!==1||!d(g).is(e)))g.nodeType===1&&f.push(g),g=g[c];return f},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var R=/ jQuery\d+="(?:\d+|null)"/g,S=/^\s+/,T=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,U=/<([\w:]+)/,V=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};Z.optgroup=Z.option,Z.tbody=Z.tfoot=Z.colgroup=Z.caption=Z.thead,Z.th=Z.td,d.support.htmlSerialize||(Z._default=[1,"div
","
"]),d.fn.extend({text:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.text(a.call(this,b,c.text()))});if(typeof a!=="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return d.text(this)},wrapAll:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapAll(a.call(this,b))});if(this[0]){var b=d(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapInner(a.call(this,b))});return this.each(function(){var b=d(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){d(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){d.nodeName(this,"body")||d(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=d(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,d(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,e;(e=this[c])!=null;c++)if(!a||d.filter(a,[e]).length)!b&&e.nodeType===1&&(d.cleanData(e.getElementsByTagName("*")),d.cleanData([e])),e.parentNode&&e.parentNode.removeChild(e);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&d.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return d.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(R,""):null;if(typeof a!=="string"||X.test(a)||!d.support.leadingWhitespace&&S.test(a)||Z[(U.exec(a)||["",""])[1].toLowerCase()])d.isFunction(a)?this.each(function(b){var c=d(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);else{a=a.replace(T,"<$1>");try{for(var c=0,e=this.length;c1&&l0?this.clone(!0):this).get();d(f[h])[b](j),e=e.concat(j)}return this.pushStack(e,a,f.selector)}}),d.extend({clone:function(a,b,c){var e=a.cloneNode(!0),f,g,h;if((!d.support.noCloneEvent||!d.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!d.isXMLDoc(a)){ba(a,e),f=bb(a),g=bb(e);for(h=0;f[h];++h)ba(f[h],g[h])}if(b){_(a,e);if(c){f=bb(a),g=bb(e);for(h=0;f[h];++h)_(f[h],g[h])}}return e},clean:function(a,b,e,f){b=b||c,typeof b.createElement==="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var g=[];for(var h=0,i;(i=a[h])!=null;h++){typeof i==="number"&&(i+="");if(!i)continue;if(typeof i!=="string"||W.test(i)){if(typeof i==="string"){i=i.replace(T,"<$1>");var j=(U.exec(i)||["",""])[1].toLowerCase(),k=Z[j]||Z._default,l=k[0],m=b.createElement("div");m.innerHTML=k[1]+i+k[2];while(l--)m=m.lastChild;if(!d.support.tbody){var n=V.test(i),o=j==="table"&&!n?m.firstChild&&m.firstChild.childNodes:k[1]===""&&!n?m.childNodes:[];for(var p=o.length-1;p>=0;--p)d.nodeName(o[p],"tbody")&&!o[p].childNodes.length&&o[p].parentNode.removeChild(o[p])}!d.support.leadingWhitespace&&S.test(i)&&m.insertBefore(b.createTextNode(S.exec(i)[0]),m.firstChild),i=m.childNodes}}else i=b.createTextNode(i);i.nodeType?g.push(i):g=d.merge(g,i)}if(e)for(h=0;g[h];h++)!f||!d.nodeName(g[h],"script")||g[h].type&&g[h].type.toLowerCase()!=="text/javascript"?(g[h].nodeType===1&&g.splice.apply(g,[h+1,0].concat(d.makeArray(g[h].getElementsByTagName("script")))),e.appendChild(g[h])):f.push(g[h].parentNode?g[h].parentNode.removeChild(g[h]):g[h]);return g},cleanData:function(a){var b,c,e=d.cache,f=d.expando,g=d.event.special,h=d.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&d.noData[j.nodeName.toLowerCase()])continue;c=j[d.expando];if(c){b=e[c]&&e[c][f];if(b&&b.events){for(var k in b.events)g[k]?d.event.remove(j,k):d.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[d.expando]:j.removeAttribute&&j.removeAttribute(d.expando),delete e[c]}}}});var bd=/alpha\([^)]*\)/i,be=/opacity=([^)]*)/,bf=/-([a-z])/ig,bg=/([A-Z]|^ms)/g,bh=/^-?\d+(?:px)?$/i,bi=/^-?\d/,bj={position:"absolute",visibility:"hidden",display:"block"},bk=["Left","Right"],bl=["Top","Bottom"],bm,bn,bo,bp=function(a,b){return b.toUpperCase()};d.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return d.access(this,a,c,!0,function(a,c,e){return e!==b?d.style(a,c,e):d.css(a,c)})},d.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bm(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0},cssProps:{"float":d.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,e,f){if(a&&a.nodeType!==3&&a.nodeType!==8&&a.style){var g,h=d.camelCase(c),i=a.style,j=d.cssHooks[h];c=d.cssProps[h]||h;if(e===b){if(j&&"get"in j&&(g=j.get(a,!1,f))!==b)return g;return i[c]}if(typeof e==="number"&&isNaN(e)||e==null)return;typeof e==="number"&&!d.cssNumber[h]&&(e+="px");if(!j||!("set"in j)||(e=j.set(a,e))!==b)try{i[c]=e}catch(k){}}},css:function(a,c,e){var f,g=d.camelCase(c),h=d.cssHooks[g];c=d.cssProps[g]||g;if(h&&"get"in h&&(f=h.get(a,!0,e))!==b)return f;if(bm)return bm(a,c,g)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bf,bp)}}),d.curCSS=d.css,d.each(["height","width"],function(a,b){d.cssHooks[b]={get:function(a,c,e){var f;if(c){a.offsetWidth!==0?f=bq(a,b,e):d.swap(a,bj,function(){f=bq(a,b,e)});if(f<=0){f=bm(a,b,b),f==="0px"&&bo&&(f=bo(a,b,b));if(f!=null)return f===""||f==="auto"?"0px":f}if(f<0||f==null){f=a.style[b];return f===""||f==="auto"?"0px":f}return typeof f==="string"?f:f+"px"}},set:function(a,b){if(!bh.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),d.support.opacity||(d.cssHooks.opacity={get:function(a,b){return be.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style;c.zoom=1;var e=d.isNaN(b)?"":"alpha(opacity="+b*100+")",f=c.filter||"";c.filter=bd.test(f)?f.replace(bd,e):c.filter+" "+e}}),d(function(){d.support.reliableMarginRight||(d.cssHooks.marginRight={get:function(a,b){var c;d.swap(a,{display:"inline-block"},function(){b?c=bm(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bn=function(a,c,e){var f,g,h;e=e.replace(bg,"-$1").toLowerCase();if(!(g=a.ownerDocument.defaultView))return b;if(h=g.getComputedStyle(a,null))f=h.getPropertyValue(e),f===""&&!d.contains(a.ownerDocument.documentElement,a)&&(f=d.style(a,e));return f}),c.documentElement.currentStyle&&(bo=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bh.test(d)&&bi.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bm=bn||bo,d.expr&&d.expr.filters&&(d.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!d.support.reliableHiddenOffsets&&(a.style.display||d.css(a,"display"))==="none"},d.expr.filters.visible=function(a){return!d.expr.filters.hidden(a)});var br=/%20/g,bs=/\[\]$/,bt=/\r?\n/g,bu=/#.*$/,bv=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bw=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bx=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,by=/^(?:GET|HEAD)$/,bz=/^\/\//,bA=/\?/,bB=/)<[^<]*)*<\/script>/gi,bC=/^(?:select|textarea)/i,bD=/\s+/,bE=/([?&])_=[^&]*/,bF=/(^|\-)([a-z])/g,bG=function(a,b,c){return b+c.toUpperCase()},bH=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bI=d.fn.load,bJ={},bK={},bL,bM;try{bL=c.location.href}catch(bN){bL=c.createElement("a"),bL.href="",bL=bL.href}bM=bH.exec(bL.toLowerCase())||[],d.fn.extend({load:function(a,c,e){if(typeof a!=="string"&&bI)return bI.apply(this,arguments);if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var g=a.slice(f,a.length);a=a.slice(0,f)}var h="GET";c&&(d.isFunction(c)?(e=c,c=b):typeof c==="object"&&(c=d.param(c,d.ajaxSettings.traditional),h="POST"));var i=this;d.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?d("
").append(c.replace(bB,"")).find(g):c)),e&&i.each(e,[c,b,a])}});return this},serialize:function(){return d.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?d.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bC.test(this.nodeName)||bw.test(this.type))}).map(function(a,b){var c=d(this).val();return c==null?null:d.isArray(c)?d.map(c,function(a,c){return{name:b.name,value:a.replace(bt,"\r\n")}}):{name:b.name,value:c.replace(bt,"\r\n")}}).get()}}),d.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){d.fn[b]=function(a){return this.bind(b,a)}}),d.each(["get","post"],function(a,c){d[c]=function(a,e,f,g){d.isFunction(e)&&(g=g||f,f=e,e=b);return d.ajax({type:c,url:a,data:e,success:f,dataType:g})}}),d.extend({getScript:function(a,c){return d.get(a,b,c,"script")},getJSON:function(a,b,c){return d.get(a,b,c,"json")},ajaxSetup:function(a,b){b?d.extend(!0,a,d.ajaxSettings,b):(b=a,a=d.extend(!0,d.ajaxSettings,b));for(var c in {context:1,url:1})c in b?a[c]=b[c]:c in d.ajaxSettings&&(a[c]=d.ajaxSettings[c]);return a},ajaxSettings:{url:bL,isLocal:bx.test(bM[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":d.parseJSON,"text xml":d.parseXML}},ajaxPrefilter:bO(bJ),ajaxTransport:bO(bK),ajax:function(a,c){function v(a,c,l,n){if(r!==2){r=2,p&&clearTimeout(p),o=b,m=n||"",u.readyState=a?4:0;var q,t,v,w=l?bR(e,u,l):b,x,y;if(a>=200&&a<300||a===304){if(e.ifModified){if(x=u.getResponseHeader("Last-Modified"))d.lastModified[k]=x;if(y=u.getResponseHeader("Etag"))d.etag[k]=y}if(a===304)c="notmodified",q=!0;else try{t=bS(e,w),c="success",q=!0}catch(z){c="parsererror",v=z}}else{v=c;if(!c||a)c="error",a<0&&(a=0)}u.status=a,u.statusText=c,q?h.resolveWith(f,[t,c,u]):h.rejectWith(f,[u,c,v]),u.statusCode(j),j=b,s&&g.trigger("ajax"+(q?"Success":"Error"),[u,e,q?t:v]),i.resolveWith(f,[u,c]),s&&(g.trigger("ajaxComplete",[u,e]),--d.active||d.event.trigger("ajaxStop"))}}typeof a==="object"&&(c=a,a=b),c=c||{};var e=d.ajaxSetup({},c),f=e.context||e,g=f!==e&&(f.nodeType||f instanceof d)?d(f):d.event,h=d.Deferred(),i=d._Deferred(),j=e.statusCode||{},k,l={},m,n,o,p,q,r=0,s,t,u={readyState:0,setRequestHeader:function(a,b){r||(l[a.toLowerCase().replace(bF,bG)]=b);return this},getAllResponseHeaders:function(){return r===2?m:null},getResponseHeader:function(a){var c;if(r===2){if(!n){n={};while(c=bv.exec(m))n[c[1].toLowerCase()]=c[2]}c=n[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){r||(e.mimeType=a);return this},abort:function(a){a=a||"abort",o&&o.abort(a),v(0,a);return this}};h.promise(u),u.success=u.done,u.error=u.fail,u.complete=i.done,u.statusCode=function(a){if(a){var b;if(r<2)for(b in a)j[b]=[j[b],a[b]];else b=a[u.status],u.then(b,b)}return this},e.url=((a||e.url)+"").replace(bu,"").replace(bz,bM[1]+"//"),e.dataTypes=d.trim(e.dataType||"*").toLowerCase().split(bD),e.crossDomain==null&&(q=bH.exec(e.url.toLowerCase()),e.crossDomain=q&&(q[1]!=bM[1]||q[2]!=bM[2]||(q[3]||(q[1]==="http:"?80:443))!=(bM[3]||(bM[1]==="http:"?80:443)))),e.data&&e.processData&&typeof e.data!=="string"&&(e.data=d.param(e.data,e.traditional)),bP(bJ,e,c,u);if(r===2)return!1;s=e.global,e.type=e.type.toUpperCase(),e.hasContent=!by.test(e.type),s&&d.active++===0&&d.event.trigger("ajaxStart");if(!e.hasContent){e.data&&(e.url+=(bA.test(e.url)?"&":"?")+e.data),k=e.url;if(e.cache===!1){var w=d.now(),x=e.url.replace(bE,"$1_="+w);e.url=x+(x===e.url?(bA.test(e.url)?"&":"?")+"_="+w:"")}}if(e.data&&e.hasContent&&e.contentType!==!1||c.contentType)l["Content-Type"]=e.contentType;e.ifModified&&(k=k||e.url,d.lastModified[k]&&(l["If-Modified-Since"]=d.lastModified[k]),d.etag[k]&&(l["If-None-Match"]=d.etag[k])),l.Accept=e.dataTypes[0]&&e.accepts[e.dataTypes[0]]?e.accepts[e.dataTypes[0]]+(e.dataTypes[0]!=="*"?", */*; q=0.01":""):e.accepts["*"];for(t in e.headers)u.setRequestHeader(t,e.headers[t]);if(e.beforeSend&&(e.beforeSend.call(f,u,e)===!1||r===2)){u.abort();return!1}for(t in {success:1,error:1,complete:1})u[t](e[t]);o=bP(bK,e,c,u);if(o){u.readyState=1,s&&g.trigger("ajaxSend",[u,e]),e.async&&e.timeout>0&&(p=setTimeout(function(){u.abort("timeout")},e.timeout));try{r=1,o.send(l,v)}catch(y){status<2?v(-1,y):d.error(y)}}else v(-1,"No Transport");return u},param:function(a,c){var e=[],f=function(a,b){b=d.isFunction(b)?b():b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=d.ajaxSettings.traditional);if(d.isArray(a)||a.jquery&&!d.isPlainObject(a))d.each(a,function(){f(this.name,this.value)});else for(var g in a)bQ(g,a[g],c,f);return e.join("&").replace(br,"+")}}),d.extend({active:0,lastModified:{},etag:{}});var bT=d.now(),bU=/(\=)\?(&|$)|\?\?/i;d.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return d.expando+"_"+bT++}}),d.ajaxPrefilter("json jsonp",function(b,c,e){var f=typeof b.data==="string";if(b.dataTypes[0]==="jsonp"||c.jsonpCallback||c.jsonp!=null||b.jsonp!==!1&&(bU.test(b.url)||f&&bU.test(b.data))){var g,h=b.jsonpCallback=d.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2",m=function(){a[h]=i,g&&d.isFunction(i)&&a[h](g[0])};b.jsonp!==!1&&(j=j.replace(bU,l),b.url===j&&(f&&(k=k.replace(bU,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},e.then(m,m),b.converters["script json"]=function(){g||d.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),d.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){d.globalEval(a);return a}}}),d.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),d.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var bV=d.now(),bW,bX;d.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&bZ()||b$()}:bZ,bX=d.ajaxSettings.xhr(),d.support.ajax=!!bX,d.support.cors=bX&&"withCredentials"in bX,bX=b,d.support.ajax&&d.ajaxTransport(function(a){if(!a.crossDomain||d.support.cors){var c;return{send:function(e,f){var g=a.xhr(),h,i;a.username?g.open(a.type,a.url,a.async,a.username,a.password):g.open(a.type,a.url,a.async);if(a.xhrFields)for(i in a.xhrFields)g[i]=a.xhrFields[i];a.mimeType&&g.overrideMimeType&&g.overrideMimeType(a.mimeType),!a.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(i in e)g.setRequestHeader(i,e[i])}catch(j){}g.send(a.hasContent&&a.data||null),c=function(e,i){var j,k,l,m,n;try{if(c&&(i||g.readyState===4)){c=b,h&&(g.onreadystatechange=d.noop,delete bW[h]);if(i)g.readyState!==4&&g.abort();else{j=g.status,l=g.getAllResponseHeaders(),m={},n=g.responseXML,n&&n.documentElement&&(m.xml=n),m.text=g.responseText;try{k=g.statusText}catch(o){k=""}j||!a.isLocal||a.crossDomain?j===1223&&(j=204):j=m.text?200:404}}}catch(p){i||f(-1,p)}m&&f(j,k,m,l)},a.async&&g.readyState!==4?(bW||(bW={},bY()),h=bV++,g.onreadystatechange=bW[h]=c):c()},abort:function(){c&&c(0,1)}}}});var b_={},ca=/^(?:toggle|show|hide)$/,cb=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cc,cd=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];d.fn.extend({show:function(a,b,c){var e,f;if(a||a===0)return this.animate(ce("show",3),a,b,c);for(var g=0,h=this.length;g=0;a--)c[a].elem===this&&(b&&c[a](!0),c.splice(a,1))}),b||this.dequeue();return this}}),d.each({slideDown:ce("show",1),slideUp:ce("hide",1),slideToggle:ce("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){d.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),d.extend({speed:function(a,b,c){var e=a&&typeof a==="object"?d.extend({},a):{complete:c||!c&&b||d.isFunction(a)&&a,duration:a,easing:c&&b||b&&!d.isFunction(b)&&b};e.duration=d.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in d.fx.speeds?d.fx.speeds[e.duration]:d.fx.speeds._default,e.old=e.complete,e.complete=function(){e.queue!==!1&&d(this).dequeue(),d.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig||(b.orig={})}}),d.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(d.fx.step[this.prop]||d.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=d.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function g(a){return e.step(a)}var e=this,f=d.fx;this.startTime=d.now(),this.start=a,this.end=b,this.unit=c||this.unit||(d.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,g.elem=this.elem,g()&&d.timers.push(g)&&!cc&&(cc=setInterval(f.tick,f.interval))},show:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),d(this.elem).show()},hide:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=d.now(),c=!0;if(a||b>=this.options.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),this.options.curAnim[this.prop]=!0;for(var e in this.options.curAnim)this.options.curAnim[e]!==!0&&(c=!1);if(c){if(this.options.overflow!=null&&!d.support.shrinkWrapBlocks){var f=this.elem,g=this.options;d.each(["","X","Y"],function(a,b){f.style["overflow"+b]=g.overflow[a]})}this.options.hide&&d(this.elem).hide();if(this.options.hide||this.options.show)for(var h in this.options.curAnim)d.style(this.elem,h,this.options.orig[h]);this.options.complete.call(this.elem)}return!1}var i=b-this.startTime;this.state=i/this.options.duration;var j=this.options.specialEasing&&this.options.specialEasing[this.prop],k=this.options.easing||(d.easing.swing?"swing":"linear");this.pos=d.easing[j||k](this.state,i,0,1,this.options.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update();return!0}},d.extend(d.fx,{tick:function(){var a=d.timers;for(var b=0;b
";d.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),e=b.firstChild,f=e.firstChild,h=e.nextSibling.firstChild.firstChild,this.doesNotAddBorder=f.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,f.style.position="fixed",f.style.top="20px",this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15,f.style.position=f.style.top="",e.style.overflow="hidden",e.style.position="relative",this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),d.offset.initialize=d.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;d.offset.initialize(),d.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(d.css(a,"marginTop"))||0,c+=parseFloat(d.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var e=d.css(a,"position");e==="static"&&(a.style.position="relative");var f=d(a),g=f.offset(),h=d.css(a,"top"),i=d.css(a,"left"),j=(e==="absolute"||e==="fixed")&&d.inArray("auto",[h,i])>-1,k={},l={},m,n;j&&(l=f.position()),m=j?l.top:parseInt(h,10)||0,n=j?l.left:parseInt(i,10)||0,d.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):f.css(k)}},d.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),e=ch.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(d.css(a,"marginTop"))||0,c.left-=parseFloat(d.css(a,"marginLeft"))||0,e.top+=parseFloat(d.css(b[0],"borderTopWidth"))||0,e.left+=parseFloat(d.css(b[0],"borderLeftWidth"))||0;return{top:c.top-e.top,left:c.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&(!ch.test(a.nodeName)&&d.css(a,"position")==="static"))a=a.offsetParent;return a})}}),d.each(["Left","Top"],function(a,c){var e="scroll"+c;d.fn[e]=function(c){var f=this[0],g;if(!f)return null;if(c!==b)return this.each(function(){g=ci(this),g?g.scrollTo(a?d(g).scrollLeft():c,a?c:d(g).scrollTop()):this[e]=c});g=ci(f);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:d.support.boxModel&&g.document.documentElement[e]||g.document.body[e]:f[e]}}),d.each(["Height","Width"],function(a,c){var e=c.toLowerCase();d.fn["inner"+c]=function(){return this[0]?parseFloat(d.css(this[0],e,"padding")):null},d.fn["outer"+c]=function(a){return this[0]?parseFloat(d.css(this[0],e,a?"margin":"border")):null},d.fn[e]=function(a){var f=this[0];if(!f)return a==null?null:this;if(d.isFunction(a))return this.each(function(b){var c=d(this);c[e](a.call(this,b,c[e]()))});if(d.isWindow(f)){var g=f.document.documentElement["client"+c];return f.document.compatMode==="CSS1Compat"&&g||f.document.body["client"+c]||g}if(f.nodeType===9)return Math.max(f.documentElement["client"+c],f.body["scroll"+c],f.documentElement["scroll"+c],f.body["offset"+c],f.documentElement["offset"+c]);if(a===b){var h=d.css(f,e),i=parseFloat(h);return d.isNaN(i)?h:i}return this.css(e,typeof a==="string"?a:a+"px")}}),a.jQuery=a.$=d})(window); \ No newline at end of file diff --git a/samples-and-tests/testapp/public/stylesheets/bootstrap-1.3.0/bootstrap.min.css b/samples-and-tests/testapp/public/stylesheets/bootstrap-1.3.0/bootstrap.min.css new file mode 100644 index 000000000..b555c6553 --- /dev/null +++ b/samples-and-tests/testapp/public/stylesheets/bootstrap-1.3.0/bootstrap.min.css @@ -0,0 +1,330 @@ +html,body{margin:0;padding:0;} +h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;} +table{border-collapse:collapse;border-spacing:0;} +ol,ul{list-style:none;} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted;} +a:hover,a:active{outline:0;} +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{border:0;-ms-interpolation-mode:bicubic;} +button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;} +button,input{line-height:normal;*overflow:visible;} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} +input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +html,body{background-color:#ffffff;} +body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;} +.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;*display:inline;} +.container:after{clear:both;} +.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;*display:inline;} +.container-fluid:after{clear:both;} +.container-fluid>.sidebar{float:left;width:220px;} +.container-fluid>.content{margin-left:240px;} +a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;*display:inline;} +.row:after{clear:both;} +[class*="span"]{display:inline;float:left;margin-left:20px;} +.span1{width:40px;} +.span2{width:100px;} +.span3{width:160px;} +.span4{width:220px;} +.span5{width:280px;} +.span6{width:340px;} +.span7{width:400px;} +.span8{width:460px;} +.span9{width:520px;} +.span10{width:580px;} +.span11{width:640px;} +.span12{width:700px;} +.span13{width:760px;} +.span14{width:820px;} +.span15{width:880px;} +.span16{width:940px;} +.span17{width:1000px;} +.span18{width:1060px;} +.span19{width:1120px;} +.span20{width:1180px;} +.span21{width:1240px;} +.span22{width:1300px;} +.span23{width:1360px;} +.span24{width:1420px;} +.offset1{margin-left:80px;} +.offset2{margin-left:140px;} +.offset3{margin-left:200px;} +.offset4{margin-left:260px;} +.offset5{margin-left:320px;} +.offset6{margin-left:380px;} +.offset7{margin-left:440px;} +.offset8{margin-left:500px;} +.offset9{margin-left:560px;} +.offset10{margin-left:620px;} +.offset11{margin-left:680px;} +.offset12{margin-left:740px;} +.span-one-third{width:300px;} +.span-two-thirds{width:620px;} +.offset-one-third{margin-left:340px;} +.offset-two-thirds{margin-left:660px;} +p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;} +h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;} +h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;} +h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;} +h3,h4,h5,h6{line-height:36px;} +h3{font-size:18px;}h3 small{font-size:14px;} +h4{font-size:16px;}h4 small{font-size:12px;} +h5{font-size:14px;} +h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;} +ul,ol{margin:0 0 18px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +ul{list-style:disc;} +ol{list-style:decimal;} +li{line-height:18px;color:#808080;} +ul.unstyled{list-style:none;margin-left:0;} +dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;} +dl dt{font-weight:bold;} +dl dd{margin-left:9px;} +hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;} +strong{font-style:inherit;font-weight:bold;} +em{font-style:italic;font-weight:inherit;line-height:inherit;} +.muted{color:#bfbfbf;} +blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;} +blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';} +address{display:block;line-height:18px;margin-bottom:18px;} +code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;} +pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;} +form{margin-bottom:18px;} +fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;} +form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;*display:inline;} +form .clearfix:after{clear:both;} +label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;} +label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;} +form .input{margin-left:150px;} +input[type=checkbox],input[type=radio]{cursor:pointer;} +input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;} +input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;} +select,input[type=file]{height:27px;line-height:27px;*margin-top:4px;} +select[multiple]{height:inherit;} +textarea{height:auto;} +.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} +:-moz-placeholder{color:#bfbfbf;} +::-webkit-input-placeholder{color:#bfbfbf;} +input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);} +input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);} +input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;} +form div.clearfix.error{background:#fae5e3;padding:10px 0;margin:-10px 0 10px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}form div.clearfix.error>label,form div.clearfix.error span.help-inline,form div.clearfix.error span.help-block{color:#9d261d;} +form div.clearfix.error input,form div.clearfix.error textarea{border-color:#c87872;-webkit-box-shadow:0 0 3px rgba(171, 41, 32, 0.25);-moz-box-shadow:0 0 3px rgba(171, 41, 32, 0.25);box-shadow:0 0 3px rgba(171, 41, 32, 0.25);}form div.clearfix.error input:focus,form div.clearfix.error textarea:focus{border-color:#b9554d;-webkit-box-shadow:0 0 6px rgba(171, 41, 32, 0.5);-moz-box-shadow:0 0 6px rgba(171, 41, 32, 0.5);box-shadow:0 0 6px rgba(171, 41, 32, 0.5);} +form div.clearfix.error .input-prepend span.add-on,form div.clearfix.error .input-append span.add-on{background:#f4c8c5;border-color:#c87872;color:#b9554d;} +.input-mini,input.mini,textarea.mini,select.mini{width:60px;} +.input-small,input.small,textarea.small,select.small{width:90px;} +.input-medium,input.medium,textarea.medium,select.medium{width:150px;} +.input-large,input.large,textarea.large,select.large{width:210px;} +.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;} +.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;} +textarea.xxlarge{overflow-y:auto;} +input.span1,textarea.span1,select.span1{display:inline-block;float:none;width:30px;margin-left:0;} +input.span2,textarea.span2,select.span2{display:inline-block;float:none;width:90px;margin-left:0;} +input.span3,textarea.span3,select.span3{display:inline-block;float:none;width:150px;margin-left:0;} +input.span4,textarea.span4,select.span4{display:inline-block;float:none;width:210px;margin-left:0;} +input.span5,textarea.span5,select.span5{display:inline-block;float:none;width:270px;margin-left:0;} +input.span6,textarea.span6,select.span6{display:inline-block;float:none;width:330px;margin-left:0;} +input.span7,textarea.span7,select.span7{display:inline-block;float:none;width:390px;margin-left:0;} +input.span8,textarea.span8,select.span8{display:inline-block;float:none;width:450px;margin-left:0;} +input.span9,textarea.span9,select.span9{display:inline-block;float:none;width:510px;margin-left:0;} +input.span10,textarea.span10,select.span10{display:inline-block;float:none;width:570px;margin-left:0;} +input.span11,textarea.span11,select.span11{display:inline-block;float:none;width:630px;margin-left:0;} +input.span12,textarea.span12,select.span12{display:inline-block;float:none;width:690px;margin-left:0;} +input.span13,textarea.span13,select.span13{display:inline-block;float:none;width:750px;margin-left:0;} +input.span14,textarea.span14,select.span14{display:inline-block;float:none;width:810px;margin-left:0;} +input.span15,textarea.span15,select.span15{display:inline-block;float:none;width:870px;margin-left:0;} +input.span16,textarea.span16,select.span16{display:inline-block;float:none;width:930px;margin-left:0;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;} +.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;} +.help-inline,.help-block{font-size:11px;line-height:18px;color:#bfbfbf;} +.help-inline{padding-left:5px;*position:relative;*top:-5px;} +.help-block{display:block;max-width:600px;} +.inline-inputs{color:#808080;}.inline-inputs span,.inline-inputs input{display:inline-block;} +.inline-inputs input.mini{width:60px;} +.inline-inputs input.small{width:90px;} +.inline-inputs span{padding:0 2px 0 1px;} +.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;} +.input-prepend .add-on{*margin-top:1px;} +.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;} +.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;} +.inputs-list label{display:block;float:none;width:auto;padding:0;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;} +.inputs-list label small{font-size:11px;font-weight:normal;} +.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;} +.inputs-list:first-child{padding-top:6px;} +.inputs-list li+li{padding-top:2px;} +.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;} +.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;} +.form-stacked legend{padding-left:0;} +.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;} +.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;} +.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;} +.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;} +.form-stacked .actions{margin-left:-20px;padding-left:20px;} +table{width:100%;margin-bottom:18px;padding:0;border-collapse:separate;*border-collapse:collapse;font-size:13px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;} +table th{padding-top:9px;font-weight:bold;vertical-align:middle;border-bottom:1px solid #ddd;} +table td{vertical-align:top;} +table th+th,table td+td{border-left:1px solid #ddd;} +table tr+tr td{border-top:1px solid #ddd;} +table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} +table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} +table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} +table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} +.zebra-striped tbody tr:nth-child(odd) td{background-color:#f9f9f9;} +.zebra-striped tbody tr:hover td{background-color:#f5f5f5;} +table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;} +table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);} +table .header:hover:after{visibility:visible;} +table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} +table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} +table .blue{color:#049cdb;border-bottom-color:#049cdb;} +table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;} +table .green{color:#46a546;border-bottom-color:#46a546;} +table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;} +table .red{color:#9d261d;border-bottom-color:#9d261d;} +table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;} +table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;} +table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;} +table .orange{color:#f89406;border-bottom-color:#f89406;} +table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;} +table .purple{color:#7a43b6;border-bottom-color:#7a43b6;} +table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;} +.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.topbar h3 a:hover,.topbar .brand a:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;} +.topbar h3{position:relative;} +.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;} +.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;} +.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.topbar form.pull-right{float:right;} +.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;} +.topbar input::-webkit-input-placeholder{color:#e6e6e6;} +.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;} +.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);} +.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} +.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;} +.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;} +.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);} +.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;} +.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);} +.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);} +.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;} +.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;} +.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;} +.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;} +li.menu,.dropdown{position:relative;} +a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"↓";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;} +.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;} +.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);} +.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);} +.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;} +.tabs,.pills{margin:0 0 20px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;*display:inline;} +.tabs:after,.pills:after{clear:both;} +.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;} +.tabs{float:left;width:100%;border-bottom:1px solid #ddd;}.tabs>li{position:relative;top:1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:36px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;} +.tabs>li.active>a{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;} +.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} +.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;} +.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;} +.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;} +.tab-content{clear:both;} +.pills a{margin:5px 3px 5px 0;padding:0 15px;text-shadow:0 1px 1px #ffffff;line-height:30px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{background:#00438a;color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);} +.pills .active a{background:#0069d6;color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);} +.tab-content>*,.pill-content>*{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.breadcrumb{margin:0 0 18px;padding:7px 14px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;} +.breadcrumb .divider{padding:0 5px;color:#bfbfbf;} +.breadcrumb .active a{color:#404040;} +.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;} +.hero-unit p{font-size:18px;font-weight:200;line-height:27px;} +footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;} +.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;} +.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;} +.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;} +.btn:focus{outline:1px dotted #666;} +.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.btn.small{padding:7px 9px 7px;font-size:11px;} +:root .alert-message,:root .btn{border-radius:0 \0;} +button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;} +.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);-khtml-opacity:0.2;-moz-opacity:0.2;opacity:0.2;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;} +.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{*margin-top:3px;} +.alert-message h5{line-height:18px;} +.alert-message p{margin-bottom:0;} +.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;} +.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);} +.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;} +.alert-message.block-message ul{margin-bottom:0;} +.alert-message.block-message li{color:#404040;} +.alert-message.block-message .alert-actions{margin-top:5px;} +.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;} +.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;} +.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;} +.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination li{display:inline;} +.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;} +.pagination a:hover,.pagination .active a{background-color:#c7eefe;} +.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;} +.pagination .next a{border:0;} +.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} +.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -250px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;} +.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:50%;} +.modal-header{border-bottom:1px solid #eee;padding:5px 15px;} +.modal-body{padding:15px;} +.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;*display:inline;} +.modal-footer:after{clear:both;} +.modal-footer .btn{float:right;margin-left:5px;} +.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} +.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.twipsy-arrow{position:absolute;width:0;height:0;} +.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.popover .arrow{position:absolute;width:0;height:0;} +.popover .inner{background-color:#000000;background-color:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} +.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;} +.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;} +.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;} +.label{padding:1px 3px 2px;background-color:#bfbfbf;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;} +.label.warning{background-color:#f89406;} +.label.success{background-color:#46a546;} +.label.notice{background-color:#62cffc;} +.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;*display:inline;} +.media-grid:after{clear:both;} +.media-grid li{display:inline;} +.media-grid a{float:left;padding:4px;margin:0 0 20px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;} +.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} diff --git a/samples-and-tests/testapp/public/stylesheets/main.css b/samples-and-tests/testapp/public/stylesheets/main.css new file mode 100644 index 000000000..e69de29bb diff --git a/samples-and-tests/testapp/test/Application.test.html b/samples-and-tests/testapp/test/Application.test.html new file mode 100644 index 000000000..3e04f9769 --- /dev/null +++ b/samples-and-tests/testapp/test/Application.test.html @@ -0,0 +1,7 @@ +*{ You can use plain selenium command using the selenium tag }* + +#{selenium} + // Open the home page, and check that no error occured + open('/') + assertNotTitle('Application error') +#{/selenium} \ No newline at end of file diff --git a/samples-and-tests/testapp/test/ApplicationTest.java b/samples-and-tests/testapp/test/ApplicationTest.java new file mode 100644 index 000000000..79d1df1b2 --- /dev/null +++ b/samples-and-tests/testapp/test/ApplicationTest.java @@ -0,0 +1,17 @@ +import org.junit.*; +import play.test.*; +import play.mvc.*; +import play.mvc.Http.*; +import models.*; + +public class ApplicationTest extends FunctionalTest { + + @Test + public void testThatIndexPageWorks() { + Response response = GET("/"); + assertIsOk(response); + assertContentType("text/html", response); + assertCharset(play.Play.defaultWebEncoding, response); + } + +} \ No newline at end of file diff --git a/samples-and-tests/testapp/test/BasicTest.java b/samples-and-tests/testapp/test/BasicTest.java new file mode 100644 index 000000000..892274ce6 --- /dev/null +++ b/samples-and-tests/testapp/test/BasicTest.java @@ -0,0 +1,13 @@ +import org.junit.*; +import java.util.*; +import play.test.*; +import models.*; + +public class BasicTest extends UnitTest { + + @Test + public void aVeryImportantThingToTest() { + assertEquals(2, 1 + 1); + } + +} diff --git a/samples-and-tests/testapp/test/data.yml b/samples-and-tests/testapp/test/data.yml new file mode 100644 index 000000000..77b04bb14 --- /dev/null +++ b/samples-and-tests/testapp/test/data.yml @@ -0,0 +1,7 @@ +# you describe your data using the YAML notation here +# and then load them using Fixtures.load("data.yml") + +# User(bob): +# email: bob@gmail.com +# password: secret +# fullname: Bob \ No newline at end of file diff --git a/src/play.plugins b/src/play.plugins new file mode 100644 index 000000000..e69de29bb