├── .gitignore ├── MIT-LICENSE ├── README.md ├── app.yml ├── build.gradle ├── gradle.properties ├── pom.xml └── src └── main ├── java └── uk │ └── co │ └── froot │ └── demo │ └── openid │ ├── AppConfiguration.java │ ├── OpenIDDemoService.java │ ├── auth │ ├── annotation │ │ └── RestrictedTo.java │ └── openid │ │ ├── OpenIDAuthenticator.java │ │ ├── OpenIDCredentials.java │ │ ├── OpenIDRestrictedToInjectable.java │ │ └── OpenIDRestrictedToProvider.java │ ├── core │ ├── InMemoryOpenIDCache.java │ └── InMemoryUserCache.java │ ├── health │ └── OpenIdDemoHealthCheck.java │ ├── model │ ├── BaseModel.java │ ├── ModelBuilder.java │ ├── openid │ │ ├── DiscoveryInformationMemento.java │ │ └── TokenMemento.java │ └── security │ │ ├── Authority.java │ │ └── User.java │ ├── resources │ ├── BaseResource.java │ ├── PrivateInfoResource.java │ ├── PublicErrorResource.java │ ├── PublicHomeResource.java │ ├── PublicOAuthResource.java │ ├── PublicOpenIDResource.java │ └── RuntimeExceptionMapper.java │ └── views │ └── PublicFreemarkerView.java └── resources ├── assets ├── favicon.ico ├── images │ ├── error.jpg │ ├── signin-facebook.png │ ├── signin-google.png │ ├── signin-googleplus.png │ ├── signin-linkedin.png │ ├── signin-openid.png │ ├── signin-twitter.png │ └── signin-yahoo.png └── jquery │ └── plugins │ └── openid-selector │ ├── css │ ├── openid-shadow.css │ └── openid.css │ ├── images.large │ ├── aol.gif │ ├── facebook.gif │ ├── google.gif │ ├── mailru.gif │ ├── myopenid.gif │ ├── openid.gif │ ├── rambler.gif │ ├── verisign.gif │ ├── vkontakte.gif │ ├── yahoo.gif │ └── yandex.gif │ ├── images.small │ ├── aol.ico │ ├── aol.ico.gif │ ├── aol.ico.png │ ├── blogger.ico │ ├── blogger.ico.gif │ ├── blogger.ico.png │ ├── claimid.ico │ ├── claimid.ico.gif │ ├── claimid.ico.png │ ├── clickpass.ico │ ├── clickpass.ico.gif │ ├── clickpass.ico.png │ ├── facebook.ico │ ├── facebook.ico.gif │ ├── facebook.ico.png │ ├── flickr.ico │ ├── flickr.ico.gif │ ├── flickr.ico.png │ ├── google.ico │ ├── google.ico.gif │ ├── google.ico.png │ ├── google_profile.ico │ ├── google_profile.ico.gif │ ├── google_profile.ico.png │ ├── launchpad.ico │ ├── launchpad.ico.gif │ ├── launchpad.ico.png │ ├── linkedin.ico │ ├── linkedin.ico.gif │ ├── linkedin.ico.png │ ├── livejournal.ico │ ├── livejournal.ico.gif │ ├── livejournal.ico.png │ ├── mailru.ico │ ├── mailru.ico.gif │ ├── mailru.ico.png │ ├── myopenid.ico │ ├── myopenid.ico.gif │ ├── myopenid.ico.png │ ├── openid.ico │ ├── openid.ico.gif │ ├── openid.ico.png │ ├── rambler.ico │ ├── rambler.ico.gif │ ├── rambler.ico.png │ ├── technorati.ico │ ├── technorati.ico.gif │ ├── technorati.ico.png │ ├── twitter.ico │ ├── twitter.ico.gif │ ├── twitter.ico.png │ ├── verisign.ico │ ├── verisign.ico.gif │ ├── verisign.ico.png │ ├── vidoop.ico │ ├── vidoop.ico.gif │ ├── vidoop.ico.png │ ├── vkontakte.ico │ ├── vkontakte.ico.gif │ ├── vkontakte.ico.png │ ├── winliveid.ico │ ├── winliveid.ico.gif │ ├── winliveid.ico.png │ ├── wordpress.ico │ ├── wordpress.ico.gif │ ├── wordpress.ico.png │ ├── yahoo.ico │ ├── yahoo.ico.gif │ ├── yahoo.ico.png │ ├── yandex.ico │ ├── yandex.ico.gif │ └── yandex.ico.png │ ├── images │ ├── openid-inputicon.gif │ ├── openid-providers-en.png │ └── openid-providers-ru.png │ └── js │ ├── openid-de.js │ ├── openid-en.js │ ├── openid-jp.js │ ├── openid-jquery.js │ ├── openid-ru.js │ └── openid-uk.js ├── banner.txt └── views ├── ftl ├── common │ ├── home.ftl │ ├── login.ftl │ └── markdown.ftl ├── error │ ├── 401.ftl │ ├── 404.ftl │ └── 500.ftl ├── includes │ └── common │ │ ├── cdn-scripts.ftl │ │ ├── footer.ftl │ │ ├── head.ftl │ │ ├── header.ftl │ │ ├── left-sidebar.ftl │ │ └── right-sidebar.ftl └── private │ ├── admin.ftl │ └── home.ftl └── markdown └── demo-all-elements.md /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/ 4 | .idea/ 5 | .gradle/ 6 | *.iml 7 | *.iws 8 | .DS_Store 9 | log/ 10 | target/ 11 | /build 12 | /bin 13 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012 Gary Rowe, http://gary-rowe.com/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals (see AUTHORS.txt) For the exact contribution history, 5 | see the revision history and logs, available at 6 | https://github.com/gary-rowe/MultiBitMerchant 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OAuth Dropwizard Demo 2 | 3 | This project is based on [Gary Rowe's OpenID Dropwizard sample](https://github.com/gary-rowe/DropwizardOpenID), 4 | but integrates OAuth support for authentication instead, because some 5 | applications need to be able to post to a user's timeline or access photos, 6 | etc. in their social network. OpenID is only for authentication, not 7 | authorization. OAuth can be used for authentication (as long as 8 | you request email address access) and authorization to use the 9 | social network. 10 | 11 | Note that you can also login using OpenID. OAuth as implemented in this 12 | is an alternative login method, but one that also gives you access to 13 | feeds/contacts/albums. 14 | 15 | 16 | ## OAuth Libraries 17 | 18 | Unfortunately, there's no single Java library that does everything. Every 19 | library seems to have a weakness: 20 | 21 | * [Scribe](https://github.com/fernandezpablo85/scribe-java) - only covers 22 | OAuth URL generation, access token creation 23 | from the request token and signing subsequent requests, but doesn't 24 | wrap any of the APIs (you have to call the REST endpoints yourself) 25 | * [Spring Social](http://www.springsource.org/spring-social) - provides 26 | OAuth URL generation, a servlet filter to handle the return tokens, 27 | controllers for Spring MVC, and wrappers for the social network APIs, 28 | but doesn't include Google+ APIs (though there is a 29 | [3rd party plugin](https://github.com/GabiAxel/spring-social-google) for Google+). 30 | * [SocialAuth](http://code.google.com/p/socialauth) - similar 31 | to Spring Social in that it provides OAuth handling, a servlet filter to 32 | handle the returned token, integration examples for Seam and JBoss CDI/JSF 33 | and Struts and Spring MVC, and plugins to wrapper the social network feed 34 | APIs (only Facebook, Twitter, and LinkedIn, but not Google+ oddly enough). 35 | * [Google's OAuth Library](http://code.google.com/p/google-oauth-java-client/) 36 | and [API Client Library](http://code.google.com/p/google-api-java-client/) 37 | can be used for access to Google's social network to fill in any gaps 38 | the other libraries are missing. 39 | 40 | Scribe is the most flexible, but also the most basic. 41 | SocialAuth is a middle ground where you can write plugins for accessing 42 | parts of each social network. 43 | Spring Social is the most comprehensive, but also the most complicated 44 | to hook in and is best used with other parts of the Spring Framework. 45 | 46 | SocialAuth seemed the best middle ground, so that was used for 47 | the OAuth support for this application. 48 | 49 | ## Getting started 50 | 51 | From an IDE, just run the `OpenIDDemoService.main()` with a runtime 52 | configuration that passes application parameters of `server app.yml`. 53 | 54 | From the console, just build with Maven and run it: 55 | ``` 56 | mvn clean install 57 | java -jar target/dropwizard-openid-1.1.0.jar server app.yml 58 | ``` 59 | 60 | From the console, you can also use Gradle to build it into a single jar and run 61 | ``` 62 | gradle runShadow 63 | ``` 64 | 65 | ## Proxy settings 66 | 67 | If you are behind a firewall you will need to set the proxy 68 | This is configured in the app.yml file. 69 | 70 | ## Authorization 71 | 72 | Here is an example of the authorization annotation as used in ```PrivateInfoResource```. 73 | 74 | ```java 75 | /** 76 | * @return The private home view if authenticated 77 | */ 78 | @GET 79 | @Path("/home") 80 | @Timed 81 | @CacheControl(noCache = true) 82 | public PublicFreemarkerView viewHome( 83 | @RestrictedTo(Authority.ROLE_PUBLIC) 84 | User publicUser) { 85 | 86 | BaseModel model = newBaseModel(); 87 | return new PublicFreemarkerView("private/home.ftl", model); 88 | 89 | } 90 | ``` 91 | 92 | ## Adding yourself as an "admin" 93 | 94 | The code supports different levels of authority (PUBLIC and ADMIN). 95 | To add yourself as an admin, add your email address to the list of adminUsers in app.yml. 96 | 97 | The user is uniquely identified by email address and provider ID; if you only check the 98 | email address, they might be able to find a provider that lets them enter your email address. 99 | If you're using openid, use "openid" for the provider ID. 100 | 101 | Note that you can't use a Twitter login for specifying an admin because there's no 102 | way to get the user's email address. This is because Twitter is too lazy to implement 103 | a permissions system like Facebook/Google have. [Gripe to Twitter](https://dev.twitter.com/discussions/1737) 104 | if you think this is stupid. 105 | 106 | ## General code layout 107 | 108 | * Configuration class for app.yaml is in src/main/java/OpenIDDemoConfiguration.java 109 | * The standard Dropwizard service/app is src/main/java/OpenIDDemoService.java 110 | * OpenID user classes are used for both OpenID and OAuth despite the naming (which came 111 | from Gary Rowe's original project); I did minimal renames initially, just in case these 112 | changes are merged into the original project 113 | * The in-mem user cache is in src/main/java/core/*.java 114 | * The user model is in src/main/java/model/security/User.java 115 | * The REST path handlers are in src/main/java/resources/*.java as 116 | usual for DropWizard projects. 117 | The OAuth rest handler is in src/main/java/resources/PublicOAuthResource.java. 118 | * The model used for the FreeMarker views is in src/main/java/model/BaseModel.java. 119 | It also holds anything that can be displayed on the small set of FreeMarker templates 120 | included. 121 | * The home page when you log in w/ all the social contacts/albums displayed is 122 | in src/main/resources/views/ftl/private/home.ftl. The contacts/albums should not 123 | be loaded this way in a real application because it takes one second...sometimes way 124 | over if you have a lot :-) In a real application, the social info should be AJAX 125 | loaded in the background but this was done for simpler testing. 126 | * Bootstrap was added by modifying the resources/views/ftl/includes/common header.ftl 127 | and cdn-scripts.ftl and header.ftl and footer.ftl files. 128 | 129 | ## Changes to Dropwizard-OpenID 130 | 131 | * Cleaned up warning msgs 132 | * Added Gradle support, though currently you have to comment out the 133 | shadow plugin references to import into Eclipse because of a shadow plugin 134 | bug with a create method in that plugin or a bug in the Eclipse Gradle plugin :-( 135 | * Added User to BaseResource so pages can display a logout button if you're logged in 136 | * Added Bootstrap via a CDN to make templates look better 137 | * Moved login.ftl from openid subdir to common 138 | * Added logout support 139 | * Added support for configuring OAuth providers/permissions via the app.yml 140 | * Moved /login and /logout REST APIs from /openid to / (into PublicHomeResource) 141 | * Added images for facebook/google/twitter/linkedin OAuth buttons 142 | * Added logout button and user state to all BaseModels 143 | * Added adminUsers config in app.yml 144 | * Added display of user's contact list and albums to /private/home 145 | (note that SocialAuth doesn't currently have a Google+ plugin for these) 146 | * Added proxy config to app.yml 147 | * Added custom SocialAuth OAuth properties to app.yml 148 | * Moved cookie generator to BaseResource since it's used by OpenID and OAuth 149 | -------------------------------------------------------------------------------- /app.yml: -------------------------------------------------------------------------------- 1 | # Define the HTTP settings 2 | http: 3 | port: 8080 4 | adminPort: 8081 5 | 6 | # Define proxy host settings for connecting to OpenID servers in PublicOpenIDResource 7 | ##proxyHost: proxy.domain.com 8 | ##proxyPort: 8080 9 | 10 | # Define the client settings for connecting to upstream data services 11 | httpClient: 12 | timeout: 60s # Timeout while connecting, reading, or writing (Heroku needs large value for spin-up) 13 | timeToLive: 10m # Keep connections open 14 | cookiesEnabled: true # Track cookies 15 | gzipEnabled: true # Allow for gzipped request and response entities 16 | minThreads: 1 17 | maxThreads: 128 # Thread pool for JerseyClient's async requests 18 | 19 | logging: 20 | 21 | level: WARN 22 | 23 | # Logger-specific levels. 24 | loggers: 25 | 26 | # Set specific levels 27 | "com.sun.jersey.api.client": DEBUG 28 | "com.yammer": INFO 29 | "uk.co.froot": DEBUG 30 | 31 | # ... 32 | # Settings for logging to stdout. 33 | console: 34 | 35 | # If true, write log statements to stdout. 36 | enabled: true 37 | 38 | # Do not display log statements below this threshold to stdout. 39 | threshold: ALL 40 | 41 | # Settings for logging to a file. 42 | file: 43 | 44 | # If true, write log statements to a file. 45 | enabled: false 46 | 47 | # Do not write log statements below this threshold to the file. 48 | threshold: ALL 49 | 50 | # The file to which current statements will be logged. 51 | currentLogFilename: /var/log/example/developer.log 52 | 53 | # When the log file rotates, the archived log will be renamed to this and gzipped. The 54 | # %d is replaced with the previous day (yyyy-MM-dd). Custom rolling windows can be created 55 | # by passing a SimpleDateFormat-compatible format as an argument: "%d{yyyy-MM-dd-hh}". 56 | archivedLogFilenamePattern: /var/log/example/developer-%d.log.gz 57 | 58 | # The number of archived files to keep. 59 | archivedFileCount: 5 60 | 61 | # The timezone used to format dates. HINT: USE THE DEFAULT, UTC. 62 | timeZone: UTC 63 | 64 | # Define OAuth settings 65 | oauthSuccessUrl: http://localhost:8080/oauth/verify 66 | 67 | oauthCfg: 68 | - name: googleplus 69 | prefix: googleapis.com 70 | key: 1111111.apps.googleusercontent.com 71 | secret: 22222222 72 | permissions: https://www.googleapis.com/auth/userinfo.profile,email,http://www.google.com/m8/feeds/,http://picasaweb.google.com/data/ 73 | ## Valid permissions/scopes from: http://support.google.com/a/bin/answer.py?hl=en&answer=162106 74 | - name: facebook 75 | prefix: graph.facebook.com 76 | key: 1111111 77 | secret: 222222222 78 | permissions: publish_stream,read_stream,user_photos,email,user_birthday,user_location,offline_access 79 | ## Valid permissions from https://developers.facebook.com/docs/reference/login/extended-permissions/ 80 | - name: twitter 81 | prefix: twitter.com 82 | key: 111111 83 | secret: 22222222 84 | - name: linkedin 85 | prefix: api.linkedin.com 86 | key: 11111111 87 | secret: 222222222 88 | 89 | #oauthCustomCfg: 90 | # socialauth.myprovider: org.my.socialauth.provider.SomethingImpl 91 | # socialauth.myprovider.scope: user_email 92 | 93 | # Define admin users (email address and provider uniquely identifies a user) 94 | adminUsers: 95 | test@test.com: facebook 96 | name@name.com: googleplus 97 | 98 | # 99 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.taskdefs.condition.Os 2 | 3 | apply plugin: 'shadow' 4 | 5 | apply plugin: 'java' 6 | apply plugin: 'idea' 7 | apply plugin: 'eclipse' 8 | 9 | project.sourceCompatibility = 1.7 10 | project.targetCompatibility = 1.7 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | configurations { 17 | /** Guice 2.0 has been superseded...this is in openid4java which needs to be updated **/ 18 | all*.exclude group: 'com.google.code.guice' 19 | } 20 | 21 | dependencies { 22 | compile "com.yammer.dropwizard:dropwizard-core:$dropwizardVersion" 23 | /** Authentication support **/ 24 | compile "com.yammer.dropwizard:dropwizard-auth:$dropwizardVersion" 25 | /** Resource testing support **/ 26 | compile "com.yammer.dropwizard:dropwizard-testing:$dropwizardVersion" 27 | /** HTML Freemarker views support **/ 28 | compile "com.yammer.dropwizard:dropwizard-views:$dropwizardVersion" 29 | /** REST client support for upstream data **/ 30 | compile "com.yammer.dropwizard:dropwizard-client:$dropwizardVersion" 31 | 32 | /** OpenID library **/ 33 | /** Guice 2.0 has been superseded **/ 34 | compile ('org.openid4java:openid4java-consumer:0.9.6') { 35 | exclude group: 'com.google.code.guice' 36 | } 37 | compile "com.google.inject:guice:3.0" 38 | 39 | /** OAuth library **/ 40 | compile "org.brickred:socialauth:4.+"; 41 | compile "org.brickred:socialauth-filter:2.+"; 42 | 43 | /** markdown support for web pages **/ 44 | compile "org.pegdown:pegdown:1.1.0" 45 | } 46 | 47 | 48 | jar { 49 | manifest { 50 | attributes 'Main-Class': pkgname+'.'+appclass 51 | } 52 | } 53 | 54 | /** 55 | * One-JAR plugin integration 56 | */ 57 | buildscript { 58 | repositories { 59 | maven { 60 | name 'Gradle Shadow' 61 | url 'http://dl.bintray.com/content/johnrengelman/gradle-plugins' 62 | } 63 | } 64 | dependencies { 65 | classpath 'org.gradle.plugins:shadow:0.7.4' 66 | } 67 | } 68 | 69 | import org.gradle.api.plugins.shadow.transformers.AppendingTransformer 70 | shadow { 71 | exclude 'META-INF/*.SF' 72 | exclude 'META-INF/*.DSA' 73 | exclude 'META-INF/*.RSA' 74 | transformer(AppendingTransformer) { 75 | resource = 'META-INF/spring.handlers' 76 | } 77 | transformer(AppendingTransformer) { 78 | resource = 'META-INF/spring.schemas' 79 | } 80 | } 81 | 82 | task run(dependsOn: 'classes', type: JavaExec) { 83 | main = pkgname+'.'+appclass 84 | classpath = sourceSets.main.runtimeClasspath 85 | args 'server','app.yml' 86 | } 87 | 88 | project.ext { 89 | oneJarName = jarprefix+'-'+version+'-standalone.jar' 90 | shadowJarName = jarprefix+'-'+version+'-shadow.jar' 91 | } 92 | 93 | task runShadow(type:Exec, dependsOn: 'shadow') { 94 | workingDir = "build/libs" 95 | commandLine = ['java', '-jar', '-server', project.shadowJarName,'server','../../app.yml'] 96 | } 97 | 98 | task bdd(type: Exec, dependsOn: 'shadow') { 99 | description = "Tests BDDs against the running application" 100 | 101 | // fix to run cucumber on a particular infernal OS properly 102 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 103 | commandLine 'cmd', '/c', 'bundle.bat', 'exec', 'cucumber' 104 | } 105 | else { 106 | commandLine = ['cucumber'] 107 | } 108 | workingDir = "./src/test/resources/bdd" 109 | def jarProcess = null 110 | 111 | doFirst{ 112 | println "Starting application on separate thread..." 113 | Thread.startDaemon { 114 | jarProcess = 'java -jar -server build/libs/'+project.shadowJarName+' server app.yml'.execute() 115 | } 116 | println "Waiting for application to start before executing tests..." 117 | addShutdownHook { 118 | println "Shutting down application" 119 | jarProcess.destroy() 120 | } 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #org.gradle.daemon=true 2 | 3 | version = 1.2.0 4 | pkgname = uk.co.froot.demo.openid 5 | appclass = OpenIDDemoService 6 | jarprefix = dropwizard-oauth-openid 7 | 8 | dropwizardVersion = 0.6.2 9 | 10 | springVersion = 3.2.4.RELEASE 11 | springSecurityVersion = 3.1.4.RELEASE 12 | 13 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.1.0 5 | 6 | uk.co.froot.demo 7 | dropwizard-openid 8 | 1.2.0 9 | 10 | http://localhost:8080 11 | 12 | OpenId/OAuth Service 13 | A demo using Dropwizard and OpenId4Java/SocialAuth 14 | 2013 15 | 16 | 17 | 18 | 0.6.2 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.apache.maven.plugins 28 | maven-compiler-plugin 29 | 2.3 30 | 31 | 1.6 32 | 1.6 33 | true 34 | 35 | 36 | 37 | 38 | 39 | org.apache.maven.plugins 40 | maven-shade-plugin 41 | 1.6 42 | 43 | true 44 | 45 | 46 | *:* 47 | 48 | META-INF/*.SF 49 | META-INF/*.DSA 50 | META-INF/*.RSA 51 | 52 | 53 | 54 | 55 | 56 | 57 | package 58 | 59 | shade 60 | 61 | 62 | 63 | 64 | 65 | uk.co.froot.demo.openid.OpenIDDemoService 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | com.yammer.dropwizard 81 | dropwizard-core 82 | ${dropwizard.version} 83 | 84 | 85 | 86 | com.yammer.dropwizard 87 | dropwizard-auth 88 | ${dropwizard.version} 89 | 90 | 91 | 92 | com.yammer.dropwizard 93 | dropwizard-testing 94 | ${dropwizard.version} 95 | 96 | 97 | 98 | com.yammer.dropwizard 99 | dropwizard-views 100 | ${dropwizard.version} 101 | 102 | 103 | 104 | com.yammer.dropwizard 105 | dropwizard-client 106 | ${dropwizard.version} 107 | 108 | 109 | 110 | 111 | org.openid4java 112 | openid4java-consumer 113 | 0.9.6 114 | pom 115 | 116 | 117 | 118 | com.google.code.guice 119 | guice 120 | 121 | 122 | 123 | 124 | 125 | 126 | org.brickred 127 | socialauth 128 | 4.2 129 | 130 | 131 | org.brickred 132 | socialauth-filter 133 | 2.4 134 | 135 | 136 | 137 | 138 | com.google.inject 139 | guice 140 | 3.0 141 | 142 | 143 | 144 | 145 | org.pegdown 146 | pegdown 147 | 1.1.0 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/AppConfiguration.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Properties; 7 | 8 | import javax.validation.Valid; 9 | import javax.validation.constraints.NotNull; 10 | 11 | import org.hibernate.validator.constraints.NotEmpty; 12 | 13 | import com.fasterxml.jackson.annotation.JsonProperty; 14 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 15 | import com.yammer.dropwizard.client.JerseyClientConfiguration; 16 | import com.yammer.dropwizard.config.Configuration; 17 | 18 | /** 19 | *

DropWizard Configuration to provide the following to application:

20 | * 23 | * 24 | * @since 0.0.1 25 | *   26 | */ 27 | public class AppConfiguration extends Configuration { 28 | 29 | /** 30 | * The cookie name for the session token 31 | */ 32 | public static final String SESSION_TOKEN_NAME ="OpenIDDemo-Session"; 33 | 34 | @NotEmpty 35 | @JsonProperty 36 | private String assetCachePolicy="maximumSize=10000, expireAfterAccess=5s"; 37 | 38 | /** 39 | * How long a session cookie authentication can remain inactive before the user must signin in 40 | * TODO Implement this 41 | */ 42 | @NotEmpty 43 | @JsonProperty 44 | private String cookieAuthenticationCachePolicy ="maximumSize=10000, expireAfterAccess=600s"; 45 | 46 | @Valid 47 | @NotNull 48 | @JsonProperty 49 | private JerseyClientConfiguration httpClient = new JerseyClientConfiguration(); 50 | 51 | public String getAssetCachePolicy() { 52 | return assetCachePolicy; 53 | } 54 | 55 | public JerseyClientConfiguration getJerseyClientConfiguration() { 56 | return httpClient; 57 | } 58 | 59 | public String getCookieAuthenticationCachePolicy() { 60 | return cookieAuthenticationCachePolicy; 61 | } 62 | 63 | @JsonProperty private String oauthSuccessUrl = ""; 64 | public String getOAuthSuccessUrl() { 65 | return oauthSuccessUrl; 66 | } 67 | 68 | public static class OAuthCfgClass { 69 | @JsonProperty private String url; 70 | @JsonProperty private String name; 71 | @JsonProperty private String prefix; 72 | @JsonProperty private String key; 73 | @JsonProperty private String secret; 74 | @JsonProperty private String permissions; 75 | 76 | public String getUrl() { 77 | return url; 78 | } 79 | public String getName() { 80 | return name; 81 | } 82 | public String getPrefix() { 83 | return prefix; 84 | } 85 | public String getKey() { 86 | return key; 87 | } 88 | public String getSecret() { 89 | return secret; 90 | } 91 | public String getPermissions() { 92 | return permissions; 93 | } 94 | } 95 | @JsonDeserialize(contentAs = OAuthCfgClass.class) 96 | private List oauthCfg; 97 | public List getOAuthCfg() { 98 | return oauthCfg; 99 | } 100 | 101 | @JsonProperty 102 | private HashMap oauthCustomCfg = null; 103 | public Map OAuthCustomCfg() { 104 | return oauthCustomCfg; 105 | } 106 | 107 | public Properties getOAuthCfgProperties() { 108 | Properties properties = new Properties(); 109 | for (OAuthCfgClass oauth : oauthCfg) { 110 | properties.put(oauth.getPrefix() + ".consumer_key", 111 | oauth.getKey()); 112 | properties.put(oauth.getPrefix() + ".consumer_secret", 113 | oauth.getSecret()); 114 | if (oauth.getPermissions() != null) { 115 | properties.put(oauth.getPrefix() + ".custom_permissions", 116 | oauth.getPermissions()); 117 | } 118 | } 119 | if (oauthCustomCfg != null) { 120 | // add any custom config strings 121 | properties.putAll(oauthCustomCfg); 122 | } 123 | return properties; 124 | } 125 | 126 | @JsonProperty 127 | private HashMap adminUsers = null; 128 | public Map getAdminUsers() { 129 | return adminUsers; 130 | } 131 | 132 | @JsonProperty 133 | private String proxyHost = null; 134 | public String getProxyHost() { 135 | return proxyHost; 136 | } 137 | 138 | @JsonProperty 139 | private int proxyPort = 8080; 140 | public int getProxyPort() { 141 | return proxyPort; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/OpenIDDemoService.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid; 2 | 3 | import com.yammer.dropwizard.Service; 4 | import com.yammer.dropwizard.assets.AssetsBundle; 5 | import com.yammer.dropwizard.config.Bootstrap; 6 | import com.yammer.dropwizard.config.Environment; 7 | import com.yammer.dropwizard.views.ViewBundle; 8 | import com.yammer.dropwizard.views.ViewMessageBodyWriter; 9 | import org.eclipse.jetty.server.session.SessionHandler; 10 | import uk.co.froot.demo.openid.auth.openid.OpenIDAuthenticator; 11 | import uk.co.froot.demo.openid.auth.openid.OpenIDRestrictedToProvider; 12 | import uk.co.froot.demo.openid.model.security.User; 13 | import uk.co.froot.demo.openid.resources.PublicHomeResource; 14 | 15 | /** 16 | *

17 | * Service to provide the following to application: 18 | *

19 | *
    20 | *
  • Provision of access to resources
  • 21 | *
22 | *

23 | * Use java -jar mbm-develop-SNAPSHOT.jar server openid-demo.yml to 24 | * start the demo 25 | *

26 | * 27 | * @since 0.0.1   28 | */ 29 | public class OpenIDDemoService extends Service { 30 | private static AppConfiguration cfg; 31 | 32 | public static AppConfiguration getConfig() { 33 | return cfg; 34 | } 35 | 36 | /** 37 | * Main entry point to the application 38 | * 39 | * @param args 40 | * CLI arguments 41 | * @throws Exception 42 | */ 43 | public static void main(String[] args) throws Exception { 44 | new OpenIDDemoService().run(args); 45 | } 46 | 47 | private OpenIDDemoService() { 48 | 49 | } 50 | 51 | @Override 52 | public void initialize(Bootstrap configBootstrap) { 53 | 54 | // Bundles 55 | configBootstrap 56 | .addBundle(new AssetsBundle("/assets/images", "/images")); 57 | configBootstrap 58 | .addBundle(new AssetsBundle("/assets/jquery", "/jquery")); 59 | configBootstrap.addBundle(new ViewBundle()); 60 | } 61 | 62 | @Override 63 | public void run(AppConfiguration appConfiguration, Environment environment) 64 | throws Exception { 65 | // save config so it can be used elseware 66 | cfg = appConfiguration; 67 | 68 | // Configure authenticator 69 | OpenIDAuthenticator authenticator = new OpenIDAuthenticator(); 70 | 71 | // Configure environment 72 | environment 73 | .scanPackagesForResourcesAndProviders(PublicHomeResource.class); 74 | 75 | // Health checks 76 | environment 77 | .addHealthCheck(new uk.co.froot.demo.openid.health.OpenIdDemoHealthCheck()); 78 | 79 | // Providers 80 | environment.addProvider(new ViewMessageBodyWriter()); 81 | environment.addProvider(new OpenIDRestrictedToProvider( 82 | authenticator, "OpenID")); 83 | 84 | // Session handler 85 | environment.setSessionHandler(new SessionHandler()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/auth/annotation/RestrictedTo.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.auth.annotation; 2 | 3 | import uk.co.froot.demo.openid.model.security.Authority; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | *

Annotation to provide the following to application:

9 | *
    10 | *
  • Concise type-safe reference to {@link uk.co.froot.demo.openid.model.security.Authority}
  • 11 | *
  • Binds to parameter to assist injection of User
  • 12 | *
13 | *

Example:

14 | * {@code 15 | * public void doSomething( 16 | * @RestrictedTo({ROLE_ADMIN, ROLE_PUBLIC}) 17 | * User user 18 | * ) 19 | * } 20 | *

Would require a User with both authorities to be able to access the method

21 | * The default Authority is ROLE_ADMIN indicating that if no authority is specified then only an admin can reach the resource (fail safe). 22 | * 23 | * @since 0.0.1 24 | *   25 | */ 26 | 27 | @Documented 28 | @Retention(RetentionPolicy.RUNTIME) 29 | @Target({ElementType.PARAMETER}) 30 | public @interface RestrictedTo { 31 | Authority[] value() default Authority.ROLE_ADMIN; 32 | } -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/auth/openid/OpenIDAuthenticator.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.auth.openid; 2 | 3 | import com.google.common.base.Optional; 4 | import com.yammer.dropwizard.auth.AuthenticationException; 5 | import com.yammer.dropwizard.auth.Authenticator; 6 | import uk.co.froot.demo.openid.core.InMemoryUserCache; 7 | import uk.co.froot.demo.openid.model.security.User; 8 | 9 | /** 10 | *

Authenticator to provide the following to application:

11 | *
    12 | *
  • Verifies the provided credentials are valid
  • 13 | *
14 | * 15 | * @since 0.0.1 16 | */ 17 | public class OpenIDAuthenticator implements Authenticator { 18 | 19 | @Override 20 | public Optional authenticate(OpenIDCredentials credentials) throws AuthenticationException { 21 | 22 | // Get the User referred to by the API key 23 | Optional user = InMemoryUserCache 24 | .INSTANCE 25 | .getBySessionToken(credentials.getSessionToken()); 26 | if (!user.isPresent()) { 27 | return Optional.absent(); 28 | } 29 | 30 | // Check that their authorities match their credentials 31 | if (!user.get().hasAllAuthorities(credentials.getRequiredAuthorities())) { 32 | return Optional.absent(); 33 | } 34 | 35 | return user; 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/auth/openid/OpenIDCredentials.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.auth.openid; 2 | 3 | import com.google.common.base.Objects; 4 | import uk.co.froot.demo.openid.model.security.Authority; 5 | 6 | import java.util.Set; 7 | import java.util.UUID; 8 | 9 | import static com.google.common.base.Preconditions.checkNotNull; 10 | 11 | /** 12 | *

Value object to provide the following to {@link OpenIDAuthenticator}:

13 | *
    14 | *
  • Storage of the necessary credentials for OpenID authentication
  • 15 | *
16 | * 17 | * @since 0.0.1 18 | */ 19 | public class OpenIDCredentials { 20 | 21 | private final UUID sessionToken; 22 | private final Set requiredAuthorities; 23 | 24 | /** 25 | * @param sessionToken The session token acting as a surrogate for the OpenID token 26 | * @param requiredAuthorities The authorities required to authenticate (provided by the {@link uk.co.froot.demo.openid.auth.annotation.RestrictedTo} annotation) 27 | */ 28 | public OpenIDCredentials( 29 | UUID sessionToken, 30 | Set requiredAuthorities) { 31 | this.sessionToken = checkNotNull(sessionToken); 32 | this.requiredAuthorities = checkNotNull(requiredAuthorities); 33 | } 34 | 35 | /** 36 | * @return The OpenID token 37 | */ 38 | public UUID getSessionToken() { 39 | return sessionToken; 40 | } 41 | 42 | /** 43 | * @return The authorities required to successfully authenticate 44 | */ 45 | public Set getRequiredAuthorities() { 46 | return requiredAuthorities; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object obj) { 51 | if (this == obj) { 52 | return true; 53 | } 54 | if ((obj == null) || (getClass() != obj.getClass())) { 55 | return false; 56 | } 57 | final OpenIDCredentials that = (OpenIDCredentials) obj; 58 | 59 | return sessionToken.equals(that.sessionToken); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return (31 * sessionToken.hashCode()); 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return Objects.toStringHelper(this) 70 | .add("sessionId", sessionToken) 71 | .add("authorities", requiredAuthorities) 72 | .toString(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/auth/openid/OpenIDRestrictedToInjectable.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.auth.openid; 2 | 3 | 4 | import com.google.common.base.Optional; 5 | import com.google.common.collect.Sets; 6 | import com.sun.jersey.api.core.HttpContext; 7 | import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable; 8 | import com.yammer.dropwizard.auth.AuthenticationException; 9 | import com.yammer.dropwizard.auth.Authenticator; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import uk.co.froot.demo.openid.AppConfiguration; 13 | import uk.co.froot.demo.openid.model.security.Authority; 14 | 15 | import javax.ws.rs.WebApplicationException; 16 | import javax.ws.rs.core.Cookie; 17 | import javax.ws.rs.core.Response; 18 | import java.util.Arrays; 19 | import java.util.Map; 20 | import java.util.Set; 21 | import java.util.UUID; 22 | 23 | /** 24 | *

Injectable to provide the following to {@link OpenIDRestrictedToProvider}:

25 | *
    26 | *
  • Performs decode from HTTP request
  • 27 | *
  • Carries OpenID authentication data
  • 28 | *
29 | * 30 | * @since 0.0.1 31 | */ 32 | class OpenIDRestrictedToInjectable extends AbstractHttpContextInjectable { 33 | 34 | private static final Logger log = LoggerFactory.getLogger(OpenIDRestrictedToInjectable.class); 35 | 36 | private final Authenticator authenticator; 37 | private final String realm; 38 | private final Set requiredAuthorities; 39 | 40 | /** 41 | * @param authenticator The Authenticator that will compare credentials 42 | * @param realm The authentication realm 43 | * @param requiredAuthorities The required authorities as provided by the RestrictedTo annotation 44 | */ 45 | OpenIDRestrictedToInjectable( 46 | Authenticator authenticator, 47 | String realm, 48 | Authority[] requiredAuthorities) { 49 | this.authenticator = authenticator; 50 | this.realm = realm; 51 | this.requiredAuthorities = Sets.newHashSet(Arrays.asList(requiredAuthorities)); 52 | } 53 | 54 | public Authenticator getAuthenticator() { 55 | return authenticator; 56 | } 57 | 58 | public String getRealm() { 59 | return realm; 60 | } 61 | 62 | public Set getRequiredAuthorities() { 63 | return requiredAuthorities; 64 | } 65 | 66 | @Override 67 | public T getValue(HttpContext httpContext) { 68 | 69 | try { 70 | 71 | // Get the Authorization header 72 | final Map cookieMap = httpContext.getRequest().getCookies(); 73 | if (!cookieMap.containsKey(AppConfiguration.SESSION_TOKEN_NAME)) { 74 | throw new WebApplicationException(Response.Status.UNAUTHORIZED); 75 | } 76 | 77 | UUID sessionToken = UUID.fromString(cookieMap.get(AppConfiguration.SESSION_TOKEN_NAME).getValue()); 78 | 79 | if (sessionToken != null) { 80 | 81 | final OpenIDCredentials credentials = new OpenIDCredentials(sessionToken, requiredAuthorities); 82 | 83 | final Optional result = authenticator.authenticate(credentials); 84 | if (result.isPresent()) { 85 | return result.get(); 86 | } 87 | } 88 | } catch (IllegalArgumentException e) { 89 | log.debug("Error decoding credentials",e); 90 | } catch (AuthenticationException e) { 91 | log.warn("Error authenticating credentials",e); 92 | throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); 93 | } 94 | 95 | // Must have failed to be here 96 | throw new WebApplicationException(Response.Status.UNAUTHORIZED); 97 | } 98 | 99 | } 100 | 101 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/auth/openid/OpenIDRestrictedToProvider.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.auth.openid; 2 | 3 | 4 | import com.sun.jersey.api.model.Parameter; 5 | import com.sun.jersey.core.spi.component.ComponentContext; 6 | import com.sun.jersey.core.spi.component.ComponentScope; 7 | import com.sun.jersey.spi.inject.Injectable; 8 | import com.sun.jersey.spi.inject.InjectableProvider; 9 | import com.yammer.dropwizard.auth.Authenticator; 10 | import uk.co.froot.demo.openid.auth.annotation.RestrictedTo; 11 | 12 | /** 13 | *

Authentication provider to provide the following to Jersey:

14 | *
    15 | *
  • Bridge between Dropwizard and Jersey for HMAC authentication
  • 16 | *
  • Provides additional {@link uk.co.froot.demo.openid.model.security.Authority} information
  • 17 | *
18 | * 19 | * @param the principal type. 20 | * @since 0.0.1 21 | */ 22 | public class OpenIDRestrictedToProvider implements InjectableProvider { 23 | 24 | private final Authenticator authenticator; 25 | private final String realm; 26 | 27 | /** 28 | * Creates a new {@link OpenIDRestrictedToProvider} with the given {@link com.yammer.dropwizard.auth.Authenticator} and realm. 29 | * 30 | * @param authenticator the authenticator which will take the {@link OpenIDCredentials} and 31 | * convert them into instances of {@code T} 32 | * @param realm the name of the authentication realm 33 | */ 34 | public OpenIDRestrictedToProvider(Authenticator authenticator, String realm) { 35 | this.authenticator = authenticator; 36 | this.realm = realm; 37 | } 38 | 39 | @Override 40 | public ComponentScope getScope() { 41 | return ComponentScope.PerRequest; 42 | } 43 | 44 | @Override 45 | public Injectable getInjectable(ComponentContext ic, 46 | RestrictedTo a, 47 | Parameter c) { 48 | return new OpenIDRestrictedToInjectable(authenticator, realm, a.value()); 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/core/InMemoryOpenIDCache.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.core; 2 | 3 | import com.google.common.base.Optional; 4 | import com.google.common.cache.Cache; 5 | import com.google.common.cache.CacheBuilder; 6 | import org.openid4java.consumer.ConsumerManager; 7 | 8 | import java.util.UUID; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | *

In-memory cache to provide the following to OpenID authentication:

13 | *
    14 | *
  • Short term storage of thread local session data
  • 15 | *
16 | * 17 | * @since 0.0.1 18 | *   19 | */ 20 | public enum InMemoryOpenIDCache { 21 | 22 | INSTANCE; 23 | 24 | /** 25 | * Simple cache for {@link org.openid4java.consumer.ConsumerManager} entries 26 | */ 27 | private final Cache consumerManagerCache = CacheBuilder.newBuilder() 28 | .maximumSize(1000) 29 | .expireAfterWrite(2, TimeUnit.MINUTES) 30 | .build(); 31 | 32 | /** 33 | *

Note that this is not horizontally scalable

34 | * 35 | * @param sessionToken The session token 36 | * 37 | * @return The mapped OpenID {@link org.openid4java.consumer.ConsumerManager} created for that session token 38 | */ 39 | public Optional getConsumerManager(UUID sessionToken) { 40 | 41 | return Optional.fromNullable(consumerManagerCache.getIfPresent(sessionToken)); 42 | 43 | } 44 | 45 | /** 46 | *

Note that this is not horizontally scalable

47 | * 48 | * @param sessionToken The session token 49 | * @param consumerManager The OpenID ConsumerManager for this UUID 50 | */ 51 | public void putConsumerManager(UUID sessionToken, ConsumerManager consumerManager) { 52 | 53 | consumerManagerCache.put(sessionToken, consumerManager); 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/core/InMemoryUserCache.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.core; 2 | 3 | import com.google.common.base.Optional; 4 | import com.google.common.base.Preconditions; 5 | import com.google.common.cache.Cache; 6 | import com.google.common.cache.CacheBuilder; 7 | import uk.co.froot.demo.openid.model.security.User; 8 | 9 | import java.util.Map; 10 | import java.util.UUID; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | *

Cache to provide the following to {@link User} authenticators:

15 | *
    16 | *
  • In-memory thread-safe cache for client user instances
  • 17 | *
  • Provision of fast lookup for request authentication
  • 18 | *
19 | * 20 | * @since 0.0.1 21 | *   22 | */ 23 | public enum InMemoryUserCache { 24 | 25 | // Provide a global singleton for the application 26 | INSTANCE; 27 | 28 | // A lot of threads will hit this cache 29 | private volatile Cache userCache; 30 | 31 | InMemoryUserCache() { 32 | reset(15, TimeUnit.MINUTES); 33 | } 34 | 35 | /** 36 | * Resets the cache and allows the expiry time to be set (perhaps for testing) 37 | * 38 | * @param duration The duration before a user must manually authenticate through a web form due to inactivity 39 | * @param unit The {@link java.util.concurrent.TimeUnit} that duration is expressed in 40 | */ 41 | public InMemoryUserCache reset(int duration, TimeUnit unit) { 42 | 43 | // Build the cache 44 | if (userCache != null) { 45 | userCache.invalidateAll(); 46 | } 47 | 48 | // If there is no activity against a key then we want 49 | // it to be expired from the cache, but each fresh write 50 | // will reset the expiry timer 51 | userCache = CacheBuilder 52 | .newBuilder() 53 | .expireAfterWrite(duration, unit) 54 | .maximumSize(1000) 55 | .build(); 56 | 57 | return INSTANCE; 58 | } 59 | 60 | /** 61 | * @param sessionToken The session token to locate the user (not JSESSIONID) 62 | * 63 | * @return The matching User or absent 64 | */ 65 | public Optional getBySessionToken(UUID sessionToken) { 66 | 67 | // Check the cache 68 | Optional userOptional = Optional.fromNullable(userCache.getIfPresent(sessionToken)); 69 | 70 | if (userOptional.isPresent()) { 71 | // Ensure we refresh the cache on a check to maintain the session timeout 72 | userCache.put(sessionToken, userOptional.get()); 73 | } 74 | 75 | return userOptional; 76 | 77 | } 78 | 79 | /** 80 | * @param sessionToken The session token to use to locate the user 81 | * @param user The User to cache 82 | */ 83 | public void put(UUID sessionToken, User user) { 84 | 85 | Preconditions.checkNotNull(user); 86 | 87 | userCache.put(sessionToken, user); 88 | } 89 | 90 | public void hardDelete(User user) { 91 | 92 | Preconditions.checkNotNull(user); 93 | Preconditions.checkNotNull(user.getSessionToken()); 94 | 95 | userCache.invalidate(user.getSessionToken()); 96 | } 97 | 98 | public Optional getByOpenIDIdentifier(String openIDIdentifier) { 99 | 100 | Map map = userCache.asMap(); 101 | 102 | for (Map.Entry entry : map.entrySet()) { 103 | 104 | if (entry.getValue().getOpenIDIdentifier().equals(openIDIdentifier)) { 105 | return Optional.of(entry.getValue()); 106 | } 107 | 108 | } 109 | 110 | return Optional.absent(); 111 | } 112 | 113 | public Optional getByEmailAddress(String emailAddress) { 114 | Map map = userCache.asMap(); 115 | 116 | for (Map.Entry entry : map.entrySet()) { 117 | 118 | if (entry.getValue().getEmailAddress().equals(emailAddress)) { 119 | return Optional.of(entry.getValue()); 120 | } 121 | 122 | } 123 | 124 | return Optional.absent(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/health/OpenIdDemoHealthCheck.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.health; 2 | 3 | /** 4 | *

HealthCheck to provide the following to application:

5 | *
    6 | *
  • Provision of checks against a given Configuration property
  • 7 | *
8 | * 9 | * @since 0.0.1 10 | *   11 | */ 12 | 13 | import com.yammer.metrics.core.HealthCheck; 14 | 15 | public class OpenIdDemoHealthCheck extends HealthCheck { 16 | 17 | public OpenIdDemoHealthCheck() { 18 | super("OpenID Demo"); 19 | } 20 | 21 | @Override 22 | protected Result check() throws Exception { 23 | return Result.healthy(); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/model/BaseModel.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.model; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import java.util.List; 6 | 7 | import org.brickred.socialauth.Album; 8 | import org.brickred.socialauth.AuthProvider; 9 | import org.brickred.socialauth.AuthProviderFactory; 10 | import org.brickred.socialauth.Contact; 11 | import org.brickred.socialauth.plugin.AlbumsPlugin; 12 | import org.brickred.socialauth.util.AccessGrant; 13 | import org.pegdown.PegDownProcessor; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import uk.co.froot.demo.openid.AppConfiguration; 18 | import uk.co.froot.demo.openid.AppConfiguration.OAuthCfgClass; 19 | import uk.co.froot.demo.openid.OpenIDDemoService; 20 | import uk.co.froot.demo.openid.model.security.User; 21 | 22 | import com.google.common.base.Charsets; 23 | import com.google.common.io.Resources; 24 | 25 | /** 26 | *

27 | * Base class to provide the following to views: 28 | *

29 | *
    30 | *
  • Access to common data (user, adverts etc)
  • 31 | * 32 | * @since 0.0.1   33 | */ 34 | public class BaseModel { 35 | private static final Logger log = LoggerFactory 36 | .getLogger(BaseModel.class); 37 | 38 | public BaseModel() { 39 | AppConfiguration cfg = OpenIDDemoService.getConfig(); 40 | oauthCfg = cfg.getOAuthCfg(); 41 | } 42 | 43 | private User user; 44 | 45 | public User getUser() { 46 | return user; 47 | } 48 | 49 | public void setUser(User user) { 50 | this.user = user; 51 | } 52 | 53 | // list of OAuth configs is used for display on login page 54 | // probably should be moved to a new LoginPageModel 55 | private List oauthCfg; 56 | 57 | public List getOAuthCfg() { 58 | return oauthCfg; 59 | } 60 | 61 | // list of social network contacts 62 | private List contacts = null; 63 | public List getContacts() { 64 | return contacts; 65 | } 66 | 67 | // list of social network albums 68 | private List albums = null; 69 | public List getAlbums() { 70 | return albums; 71 | } 72 | 73 | /** 74 | * @return Some Markdown rendered as HTML - this is an inefficient way of 75 | * performing this operation See the 76 | * /common/markdown.ftl to see where it is displayed 77 | * 78 | * @throws IOException 79 | * If something goes wrong 80 | */ 81 | public String getMarkdownHtml() throws IOException { 82 | 83 | URL url = BaseModel.class 84 | .getResource("/views/markdown/demo-all-elements.md"); 85 | String markdown = Resources.toString(url, Charsets.UTF_8).trim(); 86 | 87 | // New processor each time due to pegdown not being thread-safe 88 | // internally 89 | PegDownProcessor processor = new PegDownProcessor(); 90 | 91 | // Return the rendered HTML 92 | return processor.markdownToHtml(markdown); 93 | 94 | } 95 | 96 | public void loadContacts() { 97 | if (getUser() != null) { 98 | try { 99 | if (getUser().getOAuthInfo() != null) { 100 | AccessGrant grant = getUser().getOAuthInfo(); 101 | AuthProvider provider = AuthProviderFactory.getInstance(grant.getProviderId(), 102 | OpenIDDemoService.getConfig().getOAuthCfgProperties()); 103 | provider.setAccessGrant(grant); 104 | provider.registerPlugins(); // this activates the plugins for this provider 105 | contacts = provider.getContactList(); 106 | if (provider.getProviderId().equals("linkedin")) { 107 | // LinkedIn profiles are returned even if private w/ a private name 108 | // so scrub weird private contacts that linkedin's API returns 109 | for (int i = (contacts.size()-1); i >= 0; i--) { 110 | Contact contact = contacts.get(i); 111 | if (contact.getFirstName().equals("private")) { 112 | contacts.remove(i); 113 | } 114 | } 115 | // patch up bogus null DisplayName, profileURLs returned by LinkedIn plugin 116 | for (Contact contact: contacts) { 117 | if (contact.getProfileUrl() == null) { 118 | contact.setProfileUrl("#"); 119 | } 120 | if ((contact.getDisplayName() == null) && 121 | (contact.getFirstName() != null) && 122 | (contact.getLastName() != null)) { 123 | contact.setDisplayName(contact.getFirstName() + " " + contact.getLastName()); 124 | } 125 | } 126 | } 127 | } 128 | } catch (Exception e) { 129 | log.error("Error loading contacts: " + e); 130 | } 131 | } 132 | } 133 | 134 | public void loadAlbums() { 135 | if (getUser() != null) { 136 | try { 137 | if (getUser().getOAuthInfo() != null) { 138 | // use the accessgrant we stored away 139 | AccessGrant grant = getUser().getOAuthInfo(); 140 | AuthProvider provider = AuthProviderFactory.getInstance(grant.getProviderId(), 141 | OpenIDDemoService.getConfig().getOAuthCfgProperties()); 142 | provider.setAccessGrant(grant); 143 | provider.registerPlugins(); // this activates the plugins for this provider 144 | if (provider.isSupportedPlugin(org.brickred.socialauth.plugin.AlbumsPlugin.class)) { 145 | AlbumsPlugin p = provider.getPlugin(org.brickred.socialauth.plugin.AlbumsPlugin.class); 146 | albums = p.getAlbums(); 147 | } 148 | } 149 | } catch (Exception e) { 150 | log.error("Error loading albums: " + e); 151 | } 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/model/ModelBuilder.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.model; 2 | 3 | import java.util.UUID; 4 | 5 | import javax.ws.rs.core.Cookie; 6 | import javax.ws.rs.core.HttpHeaders; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import uk.co.froot.demo.openid.AppConfiguration; 12 | import uk.co.froot.demo.openid.core.InMemoryUserCache; 13 | import uk.co.froot.demo.openid.model.security.User; 14 | 15 | import com.google.common.base.Optional; 16 | 17 | /** 18 | *

    Builder to provide the following to resources:

    19 | *
      20 | *
    • Utility methods to build backing models for Views
    • 21 | *
    22 | * 23 | * @since 0.0.1 24 | */ 25 | public class ModelBuilder { 26 | 27 | private static final Logger log = LoggerFactory.getLogger(ModelBuilder.class); 28 | 29 | public Optional extractSessionToken(HttpHeaders httpHeaders) { 30 | 31 | // Want to fail fast to absent() since this will get called a lot 32 | Optional sessionToken = Optional.absent(); 33 | 34 | if (httpHeaders == null) { 35 | // Might be an internal call such as an error condition 36 | return sessionToken; 37 | } 38 | if (httpHeaders.getCookies() == null) { 39 | // This is a cold user 40 | return sessionToken; 41 | } 42 | 43 | Cookie cookie = httpHeaders.getCookies().get(AppConfiguration.SESSION_TOKEN_NAME); 44 | if (cookie == null) { 45 | // This is a cold user 46 | return sessionToken; 47 | } 48 | String value = cookie.getValue(); 49 | if (value == null) { 50 | // This is a broken cookie 51 | // Rather than throw an error we can force a fresh login to fix it up 52 | return sessionToken; 53 | } 54 | 55 | // Must be OK to be here 56 | return Optional.of(UUID.fromString(value)); 57 | 58 | } 59 | 60 | /** 61 | * @return A new base model with user populated from the session token if present 62 | */ 63 | public BaseModel newBaseModel(HttpHeaders httpHeaders) { 64 | 65 | BaseModel baseModel = new BaseModel(); 66 | 67 | // Locate and populate the user by their session token (if present) 68 | addUser(httpHeaders, baseModel); 69 | 70 | return baseModel; 71 | 72 | } 73 | 74 | /** 75 | * 76 | * @param httpHeaders The HTTP headers containing the session token 77 | * @param baseModel A base model 78 | */ 79 | public void addUser(HttpHeaders httpHeaders, BaseModel baseModel) { 80 | Optional sessionToken = extractSessionToken(httpHeaders); 81 | if (sessionToken.isPresent()) { 82 | Optional user = InMemoryUserCache.INSTANCE.getBySessionToken(sessionToken.get()); 83 | if (user.isPresent()) { 84 | log.debug("Found a user to match the session token {}",sessionToken.get()); 85 | if (user.get().getSessionToken() == null) { 86 | user.get().setSessionToken(sessionToken.get()); 87 | } 88 | baseModel.setUser(user.get()); 89 | } else { 90 | log.debug("No user matching the session token {}",sessionToken.get()); 91 | } 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/model/openid/DiscoveryInformationMemento.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.model.openid; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.collect.Sets; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | *

    Memento to provide the following to OpenID authentication web handling:

    10 | *
      11 | *
    • Persistence store of the discovery information
    • 12 | *
    13 | * 14 | * @since 0.0.1 15 | *   16 | */ 17 | public class DiscoveryInformationMemento { 18 | 19 | /** 20 | * The OP endpoint URL. 21 | */ 22 | @JsonProperty 23 | String opEndpoint; 24 | 25 | /** 26 | * The claimed identifier, i.e. the user's identity key. 27 | */ 28 | @JsonProperty 29 | String claimedIdentifier; 30 | 31 | /** 32 | * The delegate, or OP-Local identifier. 33 | * The key through which the OP remembers the user's account. 34 | */ 35 | @JsonProperty 36 | String delegate; 37 | 38 | /** 39 | * The OpenID protocol version, or target service type discovered through Yadis. 40 | */ 41 | @JsonProperty 42 | String version; 43 | 44 | /** 45 | * All service types discovered for the endpoint. 46 | */ 47 | @JsonProperty 48 | Set types = Sets.newLinkedHashSet(); 49 | 50 | public String getOpEndpoint() { 51 | return opEndpoint; 52 | } 53 | 54 | public void setOpEndpoint(String opEndpoint) { 55 | this.opEndpoint = opEndpoint; 56 | } 57 | 58 | public String getClaimedIdentifier() { 59 | return claimedIdentifier; 60 | } 61 | 62 | public void setClaimedIdentifier(String claimedIdentifier) { 63 | this.claimedIdentifier = claimedIdentifier; 64 | } 65 | 66 | public String getDelegate() { 67 | return delegate; 68 | } 69 | 70 | public void setDelegate(String delegate) { 71 | this.delegate = delegate; 72 | } 73 | 74 | public String getVersion() { 75 | return version; 76 | } 77 | 78 | public void setVersion(String version) { 79 | this.version = version; 80 | } 81 | 82 | public Set getTypes() { 83 | return types; 84 | } 85 | 86 | public void setTypes(Set types) { 87 | this.types = types; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/model/openid/TokenMemento.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.model.openid; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | *

    Memento to provide the following to OAuth authorization:

    8 | *
      9 | *
    • Storage of token state
    • 10 | *
    11 | * 12 | * @since 0.0.1 13 | *   14 | */ 15 | public class TokenMemento { 16 | 17 | private final String token; 18 | private final String secret; 19 | private final String rawResponse; 20 | 21 | @JsonCreator 22 | public TokenMemento( 23 | @JsonProperty("token") String token, 24 | @JsonProperty("secret") String secret, 25 | @JsonProperty("rawResponse") String rawResponse) { 26 | this.token = token; 27 | this.secret = secret; 28 | this.rawResponse = rawResponse; 29 | } 30 | 31 | public String getToken() { 32 | return token; 33 | } 34 | 35 | public String getSecret() { 36 | return secret; 37 | } 38 | 39 | public String getRawResponse() { 40 | return rawResponse; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/model/security/Authority.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.model.security; 2 | 3 | /** 4 | *

    Enumeration to provide the following to application:

    5 | *
      6 | *
    • Provision of standard authorities grouped by Role
    • 7 | *
    8 | *

    An Authority exists to provide an enum key to be mapped into an RestrictedTo annotation.

    9 | * 10 | * @since 0.0.1 11 | *   12 | */ 13 | public enum Authority { 14 | 15 | // Naming conventions help navigation and avoid duplication 16 | // An Authority is named as VERB_SUBJECT_ENTITY 17 | // Verbs should initially follow CRUD (CREATE, RETRIEVE, UPDATE, DELETE) 18 | // Subjects are based on outward looking relationships (OWN, OTHERS) 19 | // Entities are based on primary entities (USER, CUSTOMER, CART, ITEM, INVOICE) 20 | 21 | // Roles (act as EnumSets from the fine grained authorities defined later) 22 | 23 | // Internal roles 24 | /** 25 | * The administrator role that can reach administration API functions 26 | */ 27 | ROLE_ADMIN, 28 | /** 29 | * An anonymous (public) customer 30 | */ 31 | ROLE_PUBLIC, 32 | /** 33 | * An un-authenticated customer in possession of a "remember me" token 34 | */ 35 | ROLE_PARTIAL, 36 | 37 | // End of enum 38 | ; 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/model/security/User.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.model.security; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import com.google.common.base.Objects; 7 | import com.google.common.collect.Sets; 8 | 9 | import uk.co.froot.demo.openid.model.openid.DiscoveryInformationMemento; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.io.ObjectInputStream; 15 | import java.io.ObjectOutputStream; 16 | import java.util.Set; 17 | import java.util.UUID; 18 | 19 | import org.brickred.socialauth.util.AccessGrant; 20 | 21 | /** 22 | *

    23 | * Simple representation of a User to provide the following to Resources:
    24 | *

      25 | *
    • Storage of user state
    • 26 | *
    27 | *

    28 | */ 29 | @JsonPropertyOrder({ "id", "userName", "passwordDigest", "firstName", 30 | "lastName", "emailAddress", "openIDDiscoveryInformationMemento", 31 | "openIDIdentifier", "sessionToken", "authorities" }) 32 | public class User { 33 | 34 | /** 35 | *

    36 | * Unique identifier for this entity 37 | *

    38 | */ 39 | private String id; 40 | 41 | /** 42 | *

    43 | * A username (optional for anonymity reasons) 44 | *

    45 | */ 46 | private String userName; 47 | 48 | /** 49 | *

    50 | * A user password (not plaintext and optional for anonymity reasons) 51 | *

    52 | */ 53 | @JsonProperty 54 | protected String passwordDigest = null; 55 | 56 | /** 57 | * A first name 58 | */ 59 | @JsonProperty 60 | private String firstName; 61 | 62 | /** 63 | * A last name 64 | */ 65 | @JsonProperty 66 | private String lastName; 67 | 68 | /** 69 | * An email address 70 | */ 71 | @JsonProperty 72 | private String emailAddress; 73 | 74 | /** 75 | *

    76 | * The OpenID discovery information used in phase 1 of authenticating 77 | * against an OpenID server 78 | *

    79 | *

    80 | * Once the OpenID identifier is in place, this can be safely deleted 81 | *

    82 | */ 83 | @JsonProperty 84 | private DiscoveryInformationMemento openIDDiscoveryInformationMemento; 85 | 86 | /** 87 | * An OpenID identifier that is held across sessions 88 | */ 89 | @JsonProperty 90 | private String openIDIdentifier; 91 | 92 | /** 93 | * OAuth grant info in JSON format so we don't have to keep the SocialAuth 94 | * Manager alive 95 | */ 96 | @JsonProperty 97 | private byte oauthGrantInfo[]; 98 | 99 | /** 100 | * A shared secret between the cluster and the user's browser that is 101 | * revoked when the session ends 102 | */ 103 | @JsonProperty 104 | private UUID sessionToken; 105 | 106 | /** 107 | * The authorities for this User (an unauthenticated user has no 108 | * authorities) 109 | */ 110 | @JsonProperty 111 | private Set authorities = Sets.newHashSet(); 112 | 113 | @JsonCreator 114 | public User(@JsonProperty("id") String id, 115 | @JsonProperty("sessionToken") UUID sessionToken) { 116 | 117 | this.id = id; 118 | this.sessionToken = sessionToken; 119 | } 120 | 121 | public String getId() { 122 | return id; 123 | } 124 | 125 | public void setId(String id) { 126 | this.id = id; 127 | } 128 | 129 | /** 130 | * @return The user name to authenticate with the client 131 | */ 132 | public String getUserName() { 133 | return userName; 134 | } 135 | 136 | public void setUserName(String userName) { 137 | this.userName = userName; 138 | } 139 | 140 | /** 141 | * @return The digested password to provide authentication between the user 142 | * and the client 143 | */ 144 | public String getPasswordDigest() { 145 | return passwordDigest; 146 | } 147 | 148 | /** 149 | *

    Note that it is expected that Jasypt or similar is used prior to 150 | * storage

    151 | * 152 | * @param passwordDigest 153 | * The password digest 154 | */ 155 | public void setPasswordDigest(String passwordDigest) { 156 | this.passwordDigest = passwordDigest; 157 | } 158 | 159 | public String getFirstName() { 160 | return firstName; 161 | } 162 | 163 | public void setFirstName(String firstName) { 164 | this.firstName = firstName; 165 | } 166 | 167 | public String getLastName() { 168 | return lastName; 169 | } 170 | 171 | public void setLastName(String lastName) { 172 | this.lastName = lastName; 173 | } 174 | 175 | /** 176 | * @return The user's email address 177 | */ 178 | public String getEmailAddress() { 179 | return emailAddress; 180 | } 181 | 182 | public void setEmailAddress(String emailAddress) { 183 | this.emailAddress = emailAddress; 184 | } 185 | 186 | /** 187 | * 188 | * @return The OpenID discovery information (phase 1 of authentication) 189 | */ 190 | public DiscoveryInformationMemento getOpenIDDiscoveryInformationMemento() { 191 | return openIDDiscoveryInformationMemento; 192 | } 193 | 194 | public void setOpenIDDiscoveryInformationMemento( 195 | DiscoveryInformationMemento openIDDiscoveryInformationMemento) { 196 | this.openIDDiscoveryInformationMemento = openIDDiscoveryInformationMemento; 197 | } 198 | 199 | /** 200 | * @return The OpenID identifier 201 | */ 202 | public String getOpenIDIdentifier() { 203 | return openIDIdentifier; 204 | } 205 | 206 | public void setOpenIDIdentifier(String openIDIdentifier) { 207 | this.openIDIdentifier = openIDIdentifier; 208 | } 209 | 210 | /** 211 | * @return The session key 212 | */ 213 | public UUID getSessionToken() { 214 | return sessionToken; 215 | } 216 | 217 | public void setSessionToken(UUID sessionToken) { 218 | this.sessionToken = sessionToken; 219 | } 220 | 221 | public void setAuthorities(Set authorities) { 222 | this.authorities = authorities; 223 | } 224 | 225 | public Set getAuthorities() { 226 | return authorities; 227 | } 228 | 229 | public boolean hasAllAuthorities(Set requiredAuthorities) { 230 | return authorities.containsAll(requiredAuthorities); 231 | } 232 | 233 | public boolean hasAuthority(Authority authority) { 234 | return hasAllAuthorities(Sets.newHashSet(authority)); 235 | } 236 | 237 | public void setOAuthInfo(AccessGrant oathJSON) throws IOException { 238 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 239 | ObjectOutputStream oos = new ObjectOutputStream(baos); 240 | oos.writeObject(oathJSON); 241 | oauthGrantInfo = baos.toByteArray(); 242 | } 243 | 244 | public AccessGrant getOAuthInfo() throws IOException, 245 | ClassNotFoundException { 246 | ByteArrayInputStream bios = new ByteArrayInputStream(oauthGrantInfo); 247 | ObjectInputStream ois = new ObjectInputStream(bios); 248 | AccessGrant grant = (AccessGrant) ois.readObject(); 249 | return grant; 250 | } 251 | 252 | @Override 253 | public String toString() { 254 | return Objects.toStringHelper(this).add("userName", userName) 255 | .add("password", "**********") 256 | .add("emailAddress", emailAddress) 257 | .add("openIDIdentifier", openIDIdentifier) 258 | .add("sessionToken", sessionToken).add("firstName", firstName) 259 | .add("lastName", lastName).toString(); 260 | } 261 | 262 | } 263 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/resources/BaseResource.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.resources; 2 | 3 | import java.util.Locale; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.ws.rs.core.Context; 7 | import javax.ws.rs.core.HttpHeaders; 8 | import javax.ws.rs.core.NewCookie; 9 | import javax.ws.rs.core.UriInfo; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import com.google.common.base.Optional; 15 | 16 | import uk.co.froot.demo.openid.AppConfiguration; 17 | import uk.co.froot.demo.openid.model.BaseModel; 18 | import uk.co.froot.demo.openid.model.ModelBuilder; 19 | import uk.co.froot.demo.openid.model.security.User; 20 | 21 | /** 22 | *

    Abstract base class to provide the following to subclasses:

    23 | *
      24 | *
    • Provision of common methods
    • 25 | *
    26 | * 27 | * @since 0.0.1 28 | *   29 | */ 30 | public abstract class BaseResource { 31 | private static final Logger log = LoggerFactory.getLogger(BaseResource.class); 32 | 33 | protected static final String OPENID_IDENTIFIER_KEY = "openid-identifier-key"; 34 | 35 | /** 36 | * Jersey creates a fresh resource every request so this is safe 37 | */ 38 | @Context 39 | protected UriInfo uriInfo; 40 | 41 | /** 42 | * Jersey creates a fresh resource every request so this is safe 43 | */ 44 | @Context 45 | protected HttpHeaders httpHeaders; 46 | 47 | /** 48 | * Jersey creates a fresh resource every request so this is safe 49 | */ 50 | @Context 51 | protected HttpServletRequest request; 52 | 53 | 54 | protected final ModelBuilder modelBuilder = new ModelBuilder(); 55 | 56 | public BaseResource() { 57 | 58 | } 59 | 60 | /** 61 | * @return The most appropriate locale for the upstream request (never null) 62 | */ 63 | public Locale getLocale() { 64 | // TODO This should be a configuration setting 65 | Locale defaultLocale = Locale.UK; 66 | 67 | Locale locale; 68 | if (httpHeaders == null) { 69 | locale = defaultLocale; 70 | } else { 71 | locale = httpHeaders.getLanguage(); 72 | if (locale == null) { 73 | locale = defaultLocale; 74 | } 75 | } 76 | return locale; 77 | } 78 | 79 | /** 80 | * Utility method to create a base model present on all non-authenticated resources 81 | * 82 | * @return A base model 83 | */ 84 | protected BaseModel newBaseModel() { 85 | 86 | BaseModel model = modelBuilder.newBaseModel(httpHeaders); 87 | 88 | return model; 89 | } 90 | 91 | 92 | /** 93 | * @param user A user with a session token. If absent then the cookie will be removed. 94 | * 95 | * @return A cookie with a long term expiry date suitable for use as a session token for OpenID 96 | */ 97 | protected NewCookie replaceSessionTokenCookie(Optional user) { 98 | 99 | if (user.isPresent()) { 100 | 101 | String value = user.get().getSessionToken().toString(); 102 | 103 | log.debug("Replacing session token with {}", value); 104 | 105 | return new NewCookie( 106 | AppConfiguration.SESSION_TOKEN_NAME, 107 | value, // Value 108 | "/", // Path 109 | null, // Domain 110 | null, // Comment 111 | 86400 * 30, // 30 days 112 | false); 113 | } else { 114 | // Remove the session token cookie 115 | log.debug("Removing session token"); 116 | 117 | return new NewCookie( 118 | AppConfiguration.SESSION_TOKEN_NAME, 119 | null, // Value 120 | null, // Path 121 | null, // Domain 122 | null, // Comment 123 | 0, // Expire immediately 124 | false); 125 | } 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/resources/PrivateInfoResource.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.resources; 2 | 3 | import com.yammer.dropwizard.jersey.caching.CacheControl; 4 | import com.yammer.metrics.annotation.Timed; 5 | import uk.co.froot.demo.openid.auth.annotation.RestrictedTo; 6 | import uk.co.froot.demo.openid.model.security.Authority; 7 | import uk.co.froot.demo.openid.model.BaseModel; 8 | import uk.co.froot.demo.openid.model.security.User; 9 | import uk.co.froot.demo.openid.views.PublicFreemarkerView; 10 | 11 | import javax.ws.rs.GET; 12 | import javax.ws.rs.Path; 13 | import javax.ws.rs.Produces; 14 | import javax.ws.rs.core.MediaType; 15 | 16 | /** 17 | *

    Resource to provide the following to application:

    18 | *
      19 | *
    • Provision of configuration for public home page
    • 20 | *
    21 | * 22 | * @since 0.0.1 23 | */ 24 | @Path("/private") 25 | @Produces(MediaType.TEXT_HTML) 26 | public class PrivateInfoResource extends BaseResource { 27 | 28 | /** 29 | * @return The private home view if authenticated 30 | */ 31 | @GET 32 | @Path("/home") 33 | @Timed 34 | @CacheControl(noCache = true) 35 | public PublicFreemarkerView viewHome( 36 | @RestrictedTo(Authority.ROLE_PUBLIC) 37 | User publicUser 38 | ) { 39 | 40 | BaseModel model = newBaseModel(); 41 | model.loadContacts(); 42 | model.loadAlbums(); 43 | return new PublicFreemarkerView("private/home.ftl", model); 44 | 45 | } 46 | 47 | /** 48 | * @return The private admin view if authenticated 49 | */ 50 | @GET 51 | @Path("/admin") 52 | @Timed 53 | @CacheControl(noCache = true) 54 | public PublicFreemarkerView viewAdmin( 55 | @RestrictedTo(Authority.ROLE_ADMIN) 56 | User adminUser 57 | ) { 58 | 59 | BaseModel model = newBaseModel(); 60 | return new PublicFreemarkerView("private/admin.ftl", model); 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/resources/PublicErrorResource.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.resources; 2 | 3 | import com.yammer.dropwizard.jersey.caching.CacheControl; 4 | import com.yammer.metrics.annotation.Timed; 5 | import uk.co.froot.demo.openid.model.BaseModel; 6 | import uk.co.froot.demo.openid.views.PublicFreemarkerView; 7 | 8 | import javax.ws.rs.GET; 9 | import javax.ws.rs.Path; 10 | import javax.ws.rs.Produces; 11 | import javax.ws.rs.core.MediaType; 12 | 13 | /** 14 | *

    Resource to provide the following to application:

    15 | *
      16 | *
    • Provision of error pages
    • 17 | *
    18 | * 19 | * @since 0.0.1 20 | */ 21 | @Path("/error") 22 | @Produces(MediaType.TEXT_HTML) 23 | public class PublicErrorResource extends BaseResource { 24 | 25 | /** 26 | * Provide the 401 Unauthorized page 27 | * 28 | * @return A localised view containing HTML 29 | */ 30 | @GET 31 | @Path("/401") 32 | @Timed 33 | @CacheControl(noCache = true) 34 | public PublicFreemarkerView view401() { 35 | 36 | // Populate the model 37 | BaseModel model = newBaseModel(); 38 | 39 | return new PublicFreemarkerView("error/401.ftl",model); 40 | } 41 | 42 | /** 43 | * Provide the 404 Not Found page 44 | * 45 | * @return A localised view containing HTML 46 | */ 47 | @GET 48 | @Path("/404") 49 | @Timed 50 | @CacheControl(noCache = true) 51 | public PublicFreemarkerView view404() { 52 | 53 | // Populate the model 54 | BaseModel model = newBaseModel(); 55 | 56 | return new PublicFreemarkerView("error/404.ftl",model); 57 | } 58 | 59 | /** 60 | * Provide the 500 Internal Server Error page 61 | * 62 | * @return A localised view containing HTML 63 | */ 64 | @GET 65 | @Path("/500") 66 | @Timed 67 | @CacheControl(noCache = true) 68 | public PublicFreemarkerView view500() { 69 | 70 | // Populate the model 71 | BaseModel model = newBaseModel(); 72 | 73 | return new PublicFreemarkerView("error/500.ftl",model); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/resources/PublicHomeResource.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.resources; 2 | 3 | import java.io.InputStream; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import javax.ws.rs.GET; 9 | import javax.ws.rs.Path; 10 | import javax.ws.rs.Produces; 11 | import javax.ws.rs.core.MediaType; 12 | import javax.ws.rs.core.Response; 13 | 14 | import uk.co.froot.demo.openid.core.InMemoryUserCache; 15 | import uk.co.froot.demo.openid.model.BaseModel; 16 | import uk.co.froot.demo.openid.model.ModelBuilder; 17 | import uk.co.froot.demo.openid.model.security.User; 18 | import uk.co.froot.demo.openid.views.PublicFreemarkerView; 19 | 20 | import com.google.common.base.Optional; 21 | import com.yammer.dropwizard.jersey.caching.CacheControl; 22 | import com.yammer.dropwizard.views.View; 23 | import com.yammer.metrics.annotation.Timed; 24 | 25 | /** 26 | *

    Resource to provide the following to application:

    27 | *
      28 | *
    • Provision of configuration for public home page
    • 29 | *
    30 | * 31 | * @since 0.0.1 32 | */ 33 | @Path("/") 34 | @Produces(MediaType.TEXT_HTML) 35 | public class PublicHomeResource extends BaseResource { 36 | //private static final Logger log = LoggerFactory.getLogger(PublicHomeResource.class); 37 | 38 | private final ModelBuilder modelBuilder = new ModelBuilder(); 39 | 40 | /** 41 | * Provide the initial view on to the system 42 | * 43 | * @return A localised view containing HTML 44 | */ 45 | @GET 46 | @Timed 47 | @CacheControl(noCache = true) 48 | public PublicFreemarkerView viewHome() { 49 | 50 | BaseModel model = newBaseModel(); 51 | return new PublicFreemarkerView("common/home.ftl",model); 52 | } 53 | 54 | /** 55 | * Provide the initial view on to the system 56 | * 57 | * @return A localised view containing HTML 58 | */ 59 | @GET 60 | @Path("markdown") 61 | @Timed 62 | @CacheControl(noCache = true) 63 | public PublicFreemarkerView viewMarkdown() { 64 | 65 | BaseModel model = newBaseModel(); 66 | return new PublicFreemarkerView("common/markdown.ftl",model); 67 | } 68 | 69 | /** 70 | * Provide the initial view on to the system 71 | * 72 | * @return A the favicon images from the assets 73 | */ 74 | @GET 75 | @Path("favicon.ico") 76 | @Timed 77 | @CacheControl(maxAge = 24, maxAgeUnit = TimeUnit.HOURS) 78 | public Response viewFavicon() { 79 | 80 | InputStream is = PublicHomeResource.class.getResourceAsStream("/assets/favicon.ico"); 81 | 82 | return Response.ok(is).build(); 83 | } 84 | 85 | 86 | /** 87 | * @return A login view with a session token 88 | */ 89 | @GET 90 | @Path("login") 91 | public View login() { 92 | 93 | return new PublicFreemarkerView("common/login.ftl", 94 | modelBuilder.newBaseModel(httpHeaders)); 95 | } 96 | 97 | /** 98 | * @return A login view with a session token 99 | * @throws URISyntaxException 100 | */ 101 | @GET 102 | @Path("logout") 103 | public Response logout() throws URISyntaxException { 104 | 105 | BaseModel model = modelBuilder.newBaseModel(httpHeaders); 106 | User user = model.getUser(); 107 | if (user != null) { 108 | // (We'll delete the user but really this would just be an update) 109 | InMemoryUserCache.INSTANCE.hardDelete(user); 110 | model.setUser(null); 111 | } 112 | 113 | // Remove the session token which will have the effect of logout 114 | return Response 115 | .temporaryRedirect(new URI("/")) 116 | .cookie(replaceSessionTokenCookie(Optional.absent())) 117 | .build(); 118 | 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/resources/PublicOAuthResource.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.resources; 2 | 3 | import java.net.URI; 4 | import java.net.URISyntaxException; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.UUID; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.ws.rs.GET; 11 | import javax.ws.rs.Path; 12 | import javax.ws.rs.Produces; 13 | import javax.ws.rs.QueryParam; 14 | import javax.ws.rs.WebApplicationException; 15 | import javax.ws.rs.core.Context; 16 | import javax.ws.rs.core.MediaType; 17 | import javax.ws.rs.core.Response; 18 | 19 | import org.brickred.socialauth.AuthProvider; 20 | import org.brickred.socialauth.Profile; 21 | import org.brickred.socialauth.SocialAuthConfig; 22 | import org.brickred.socialauth.SocialAuthManager; 23 | import org.brickred.socialauth.util.SocialAuthUtil; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | import uk.co.froot.demo.openid.AppConfiguration.OAuthCfgClass; 28 | import uk.co.froot.demo.openid.OpenIDDemoService; 29 | import uk.co.froot.demo.openid.core.InMemoryUserCache; 30 | import uk.co.froot.demo.openid.model.security.Authority; 31 | import uk.co.froot.demo.openid.model.security.User; 32 | 33 | import com.google.common.base.Optional; 34 | import com.yammer.metrics.annotation.Timed; 35 | 36 | /** 37 | *

    38 | * Resource to provide the following to application: 39 | *

    40 | *
      41 | *
    • OAuth authentication handling
    • 42 | *
    43 | * 44 | * @since 0.0.1 45 | */ 46 | @Path("/oauth") 47 | @Produces(MediaType.TEXT_HTML) 48 | public class PublicOAuthResource extends BaseResource { 49 | 50 | private static final Logger log = LoggerFactory 51 | .getLogger(PublicOAuthResource.class); 52 | 53 | /** 54 | * Default constructor 55 | */ 56 | public PublicOAuthResource() { 57 | } 58 | 59 | @GET 60 | @Timed 61 | @Path("/request") 62 | public Response requestOAuth(@Context HttpServletRequest request, 63 | @QueryParam("provider") String provider) throws URISyntaxException { 64 | if (provider == null) { 65 | log.debug("Missing provider ID"); 66 | throw new WebApplicationException(Response.Status.BAD_REQUEST); 67 | } 68 | // instantiate SocialAuth for this provider type and tuck into session 69 | List oauthCfg = OpenIDDemoService.getConfig() 70 | .getOAuthCfg(); 71 | if (oauthCfg != null) { 72 | // get the authentication URL for this provider 73 | try { 74 | SocialAuthManager manager = getSocialAuthManager(); 75 | request.getSession().setAttribute("authManager", manager); 76 | 77 | java.net.URI url = new URI(manager.getAuthenticationUrl( 78 | provider, OpenIDDemoService.getConfig() 79 | .getOAuthSuccessUrl())); 80 | log.debug("OAuth Auth URL: {}", url); 81 | return Response.temporaryRedirect(url).build(); 82 | } catch (Exception e) { 83 | log.error("SocialAuth error: {}", e); 84 | } 85 | } 86 | throw new WebApplicationException(Response.Status.BAD_REQUEST); 87 | } 88 | 89 | /** 90 | * Handles the OAuth server response to the earlier AuthRequest 91 | * 92 | * @return The OAuth identifier for this user if verification was 93 | * successful 94 | */ 95 | @GET 96 | @Timed 97 | @Path("/verify") 98 | public Response verifyOAuthServerResponse( 99 | @Context HttpServletRequest request) { 100 | 101 | // this was placed in the session in the /request resource 102 | SocialAuthManager manager = (SocialAuthManager) request.getSession() 103 | .getAttribute("authManager"); 104 | 105 | if (manager != null) { 106 | try { 107 | // call connect method of manager which returns the provider 108 | // object 109 | Map params = SocialAuthUtil 110 | .getRequestParametersMap(request); 111 | AuthProvider provider = manager.connect(params); 112 | 113 | // get profile 114 | Profile p = provider.getUserProfile(); 115 | 116 | log.info("Logging in user '{}'", p); 117 | 118 | // at this point, we've been validated, so save off this user's 119 | // info 120 | User tempUser = new User(null, UUID.randomUUID()); 121 | tempUser.setOpenIDIdentifier(p.getValidatedId()); 122 | tempUser.setOAuthInfo(provider.getAccessGrant()); 123 | 124 | tempUser.setEmailAddress(p.getEmail()); 125 | if ((p.getFirstName() == null) && (p.getLastName() == null)) { 126 | // Twitter doesn't return first/last name fields but does include 127 | // a fullname property we can use to generate them 128 | if (p.getFullName() != null) { 129 | String[] parts = p.getFullName().split("-"); 130 | if (parts.length > 1) { 131 | tempUser.setFirstName(parts[0]); 132 | tempUser.setLastName(parts[parts.length-1]); 133 | } else { 134 | tempUser.setFirstName(parts[0]); 135 | tempUser.setLastName(parts[0]); 136 | } 137 | } 138 | } else { 139 | tempUser.setFirstName(p.getFirstName()); 140 | tempUser.setLastName(p.getLastName()); 141 | } 142 | tempUser.setUserName(p.getFullName()); 143 | 144 | 145 | // Provide a basic authority in light of successful 146 | // authentication 147 | tempUser.getAuthorities().add(Authority.ROLE_PUBLIC); 148 | // see if this is an admin user (match email addr and provider) 149 | if ((OpenIDDemoService.getConfig().getAdminUsers() != null) 150 | && (tempUser.getEmailAddress() != null)) { 151 | Map adminUsers = OpenIDDemoService 152 | .getConfig().getAdminUsers(); 153 | if (adminUsers.containsKey(tempUser.getEmailAddress()) 154 | && (adminUsers.get(tempUser.getEmailAddress()) 155 | .equals(provider.getProviderId()))) { 156 | tempUser.getAuthorities().add(Authority.ROLE_ADMIN); 157 | } 158 | } 159 | 160 | // Search for a pre-existing User matching the temp User 161 | Optional userOptional = InMemoryUserCache.INSTANCE 162 | .getByOpenIDIdentifier(tempUser.getOpenIDIdentifier()); 163 | if (!userOptional.isPresent()) { 164 | // Persist the user with the generated session token 165 | InMemoryUserCache.INSTANCE.put(tempUser.getSessionToken(), 166 | tempUser); 167 | 168 | } else { 169 | tempUser = userOptional.get(); 170 | } 171 | 172 | return Response 173 | .temporaryRedirect(new URI("/private/home")) 174 | .cookie(replaceSessionTokenCookie(Optional.of(tempUser))) 175 | .build(); 176 | } catch (Exception e1) { 177 | log.error("Error reading profile info: {}, {}", e1.getMessage(), e1.getCause().getMessage()); 178 | } 179 | } 180 | 181 | // Must have failed to be here 182 | throw new WebApplicationException(Response.Status.UNAUTHORIZED); 183 | } 184 | 185 | /** 186 | * Gets an initialized SocialAuthManager 187 | * @return gets an initialized SocialAuthManager 188 | */ 189 | private SocialAuthManager getSocialAuthManager() { 190 | SocialAuthConfig config = SocialAuthConfig.getDefault(); 191 | try { 192 | config.load(OpenIDDemoService.getConfig().getOAuthCfgProperties()); 193 | SocialAuthManager manager = new SocialAuthManager(); 194 | manager.setSocialAuthConfig(config); 195 | return manager; 196 | } catch (Exception e) { 197 | log.error("SocialAuth error: " + e); 198 | } 199 | return null; 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/resources/PublicOpenIDResource.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.resources; 2 | 3 | import java.net.MalformedURLException; 4 | import java.net.URI; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.UUID; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.ws.rs.FormParam; 11 | import javax.ws.rs.GET; 12 | import javax.ws.rs.POST; 13 | import javax.ws.rs.Path; 14 | import javax.ws.rs.Produces; 15 | import javax.ws.rs.QueryParam; 16 | import javax.ws.rs.WebApplicationException; 17 | import javax.ws.rs.core.Context; 18 | import javax.ws.rs.core.MediaType; 19 | import javax.ws.rs.core.Response; 20 | 21 | import org.openid4java.OpenIDException; 22 | import org.openid4java.consumer.ConsumerException; 23 | import org.openid4java.consumer.ConsumerManager; 24 | import org.openid4java.consumer.VerificationResult; 25 | import org.openid4java.discovery.DiscoveryException; 26 | import org.openid4java.discovery.DiscoveryInformation; 27 | import org.openid4java.discovery.Identifier; 28 | import org.openid4java.discovery.yadis.YadisException; 29 | import org.openid4java.message.AuthRequest; 30 | import org.openid4java.message.AuthSuccess; 31 | import org.openid4java.message.MessageException; 32 | import org.openid4java.message.ParameterList; 33 | import org.openid4java.message.ax.AxMessage; 34 | import org.openid4java.message.ax.FetchRequest; 35 | import org.openid4java.message.ax.FetchResponse; 36 | import org.openid4java.util.HttpClientFactory; 37 | import org.openid4java.util.ProxyProperties; 38 | import org.slf4j.Logger; 39 | import org.slf4j.LoggerFactory; 40 | 41 | import uk.co.froot.demo.openid.OpenIDDemoService; 42 | import uk.co.froot.demo.openid.core.InMemoryOpenIDCache; 43 | import uk.co.froot.demo.openid.core.InMemoryUserCache; 44 | import uk.co.froot.demo.openid.model.BaseModel; 45 | import uk.co.froot.demo.openid.model.openid.DiscoveryInformationMemento; 46 | import uk.co.froot.demo.openid.model.security.Authority; 47 | import uk.co.froot.demo.openid.model.security.User; 48 | import uk.co.froot.demo.openid.views.PublicFreemarkerView; 49 | 50 | import com.google.common.base.Optional; 51 | import com.yammer.dropwizard.views.View; 52 | 53 | /** 54 | *

    Resource to provide the following to application:

    55 | *
      56 | *
    • Provision of configuration for public home page
    • 57 | *
    58 | * 59 | * @since 0.0.1 60 | */ 61 | @Path("/openid") 62 | @Produces(MediaType.TEXT_HTML) 63 | public class PublicOpenIDResource extends BaseResource { 64 | 65 | private static final Logger log = LoggerFactory.getLogger(PublicOpenIDResource.class); 66 | 67 | private final static String YAHOO_ENDPOINT = "https://me.yahoo.com"; 68 | private final static String GOOGLE_ENDPOINT = "https://www.google.com/accounts/o8/id"; 69 | 70 | /** 71 | * Default constructor 72 | */ 73 | public PublicOpenIDResource() { 74 | 75 | // Proxy configuration must come before ConsumerManager construction 76 | if (OpenIDDemoService.getConfig().getProxyHost() != null) { 77 | ProxyProperties proxyProps = new ProxyProperties(); 78 | proxyProps.setProxyHostName(OpenIDDemoService.getConfig().getProxyHost()); 79 | proxyProps.setProxyPort(OpenIDDemoService.getConfig().getProxyPort()); 80 | HttpClientFactory.setProxyProperties(proxyProps); 81 | } 82 | 83 | } 84 | 85 | /** 86 | * Handles the authentication request from the user after they select their OpenId server 87 | * 88 | * @param identifier The identifier for the OpenId server 89 | * 90 | * @return A redirection or a form view containing user-specific permissions 91 | */ 92 | @POST 93 | @SuppressWarnings("unchecked") 94 | public Response authenticationRequest( 95 | @Context 96 | HttpServletRequest request, 97 | @FormParam("identifier") 98 | String identifier 99 | ) { 100 | 101 | UUID sessionToken = UUID.randomUUID(); 102 | 103 | try { 104 | 105 | // The OpenId server will use this endpoint to provide authentication 106 | // Parts of this may be shown to the user 107 | final String returnToUrl; 108 | if (request.getServerPort() == 80) { 109 | returnToUrl = String.format( 110 | "http://%s/openid/verify?token=%s", 111 | request.getServerName(), 112 | sessionToken); 113 | } else { 114 | returnToUrl = String.format( 115 | "http://%s:%d/openid/verify?token=%s", 116 | request.getServerName(), 117 | request.getServerPort(), 118 | sessionToken); 119 | } 120 | 121 | log.debug("Return to URL '{}'", returnToUrl); 122 | 123 | // Create a consumer manager for this specific request and cache it 124 | // (this is to preserve session state such as nonce values etc) 125 | ConsumerManager consumerManager = new ConsumerManager(); 126 | InMemoryOpenIDCache.INSTANCE.putConsumerManager(sessionToken, consumerManager); 127 | 128 | // Perform discovery on the user-supplied identifier 129 | List discoveries = consumerManager.discover(identifier); 130 | 131 | // Attempt to associate with the OpenID provider 132 | // and retrieve one service endpoint for authentication 133 | DiscoveryInformation discovered = consumerManager.associate(discoveries); 134 | 135 | // Create a memento to rebuild the discovered information in a subsequent request 136 | DiscoveryInformationMemento memento = new DiscoveryInformationMemento(); 137 | if (discovered.getClaimedIdentifier() != null) { 138 | memento.setClaimedIdentifier(discovered.getClaimedIdentifier().getIdentifier()); 139 | } 140 | memento.setDelegate(discovered.getDelegateIdentifier()); 141 | if (discovered.getOPEndpoint() != null) { 142 | memento.setOpEndpoint(discovered.getOPEndpoint().toString()); 143 | } 144 | 145 | memento.setTypes(discovered.getTypes()); 146 | memento.setVersion(discovered.getVersion()); 147 | 148 | // Create a temporary User to preserve state between requests without 149 | // using a session (we could be in a cluster) 150 | User tempUser = new User(null, sessionToken); 151 | tempUser.setOpenIDDiscoveryInformationMemento(memento); 152 | tempUser.setSessionToken(sessionToken); 153 | 154 | // Persist the User 155 | InMemoryUserCache.INSTANCE.put(sessionToken, tempUser); 156 | 157 | // Build the AuthRequest message to be sent to the OpenID provider 158 | AuthRequest authReq = consumerManager.authenticate(discovered, returnToUrl); 159 | 160 | // Build the FetchRequest containing the information to be copied 161 | // from the OpenID provider 162 | FetchRequest fetch = FetchRequest.createFetchRequest(); 163 | // Attempt to decode each entry 164 | if (identifier.startsWith(GOOGLE_ENDPOINT)) { 165 | fetch.addAttribute("email", "http://axschema.org/contact/email", true); 166 | fetch.addAttribute("firstName", "http://axschema.org/namePerson/first", true); 167 | fetch.addAttribute("lastName", "http://axschema.org/namePerson/last", true); 168 | } else if (identifier.startsWith(YAHOO_ENDPOINT)) { 169 | fetch.addAttribute("email", "http://axschema.org/contact/email", true); 170 | fetch.addAttribute("fullname", "http://axschema.org/namePerson", true); 171 | } else { // works for myOpenID 172 | fetch.addAttribute("fullname", "http://schema.openid.net/namePerson", true); 173 | fetch.addAttribute("email", "http://schema.openid.net/contact/email", true); 174 | } 175 | 176 | // Attach the extension to the authentication request 177 | authReq.addExtension(fetch); 178 | 179 | // Redirect the user to their OpenId server authentication process 180 | return Response 181 | .seeOther(URI.create(authReq.getDestinationUrl(true))) 182 | .build(); 183 | 184 | } catch (MessageException e1) { 185 | log.error("MessageException:", e1); 186 | } catch (YadisException e1) { 187 | log.error("YaDiscoveryException:", e1); 188 | } catch (DiscoveryException e1) { 189 | log.error("DiscoveryException:", e1); 190 | } catch (ConsumerException e1) { 191 | log.error("ConsumerException:", e1); 192 | } 193 | return Response.serverError().build(); 194 | } 195 | 196 | /** 197 | * Handles the OpenId server response to the earlier AuthRequest 198 | * 199 | * @return The OpenId identifier for this user if verification was successful 200 | */ 201 | @GET 202 | @Path("/verify") 203 | public Response verifyOpenIdServerResponse( 204 | @Context HttpServletRequest request, 205 | @QueryParam("token") String rawToken) { 206 | 207 | // Retrieve the previously stored discovery information from the temporary User 208 | if (rawToken == null) { 209 | log.debug("Authentication failed due to no session token"); 210 | throw new WebApplicationException(Response.Status.UNAUTHORIZED); 211 | } 212 | 213 | // Build a session token from the request 214 | UUID sessionToken = UUID.fromString(rawToken); 215 | 216 | // Attempt to locate the consumer manager by the session token 217 | Optional consumerManagerOptional = InMemoryOpenIDCache.INSTANCE.getConsumerManager(sessionToken); 218 | if (!consumerManagerOptional.isPresent()) { 219 | log.debug("Authentication failed due to no consumer manager matching session token {}", rawToken); 220 | throw new WebApplicationException(Response.Status.UNAUTHORIZED); 221 | } 222 | ConsumerManager consumerManager = consumerManagerOptional.get(); 223 | 224 | // Attempt to locate the user by the session token 225 | Optional tempUserOptional = InMemoryUserCache.INSTANCE.getBySessionToken(sessionToken); 226 | if (!tempUserOptional.isPresent()) { 227 | log.debug("Authentication failed due to no temp User matching session token {}", rawToken); 228 | throw new WebApplicationException(Response.Status.UNAUTHORIZED); 229 | } 230 | User tempUser = tempUserOptional.get(); 231 | 232 | // Retrieve the discovery information 233 | final DiscoveryInformationMemento memento = tempUser.getOpenIDDiscoveryInformationMemento(); 234 | @SuppressWarnings("serial") 235 | Identifier identifier = new Identifier() { 236 | @Override 237 | public String getIdentifier() { 238 | return memento.getClaimedIdentifier(); 239 | } 240 | }; 241 | 242 | DiscoveryInformation discovered; 243 | try { 244 | discovered = new DiscoveryInformation( 245 | URI.create(memento.getOpEndpoint()).toURL(), 246 | identifier, 247 | memento.getDelegate(), 248 | memento.getVersion(), 249 | memento.getTypes() 250 | ); 251 | } catch (DiscoveryException e) { 252 | throw new WebApplicationException(e, Response.Status.UNAUTHORIZED); 253 | } catch (MalformedURLException e) { 254 | throw new WebApplicationException(e, Response.Status.UNAUTHORIZED); 255 | } 256 | 257 | // Extract the receiving URL from the HTTP request 258 | StringBuffer receivingURL = request.getRequestURL(); 259 | String queryString = request.getQueryString(); 260 | if (queryString != null && queryString.length() > 0) { 261 | receivingURL.append("?").append(request.getQueryString()); 262 | } 263 | log.debug("Receiving URL = '{}", receivingURL.toString()); 264 | 265 | // Extract the parameters from the authentication response 266 | // (which comes in as a HTTP request from the OpenID provider) 267 | ParameterList parameterList = new ParameterList(request.getParameterMap()); 268 | 269 | try { 270 | 271 | // Verify the response 272 | // ConsumerManager needs to be the same (static) instance used 273 | // to place the authentication request 274 | // This could be tricky if this service is load-balanced 275 | VerificationResult verification = consumerManager.verify( 276 | receivingURL.toString(), 277 | parameterList, 278 | discovered); 279 | 280 | // Examine the verification result and extract the verified identifier 281 | Optional verified = Optional.fromNullable(verification.getVerifiedId()); 282 | if (verified.isPresent()) { 283 | // Verified 284 | AuthSuccess authSuccess = (AuthSuccess) verification.getAuthResponse(); 285 | 286 | // We have successfully authenticated so remove the temp user 287 | // and replace it with a potentially new one 288 | InMemoryUserCache.INSTANCE.hardDelete(tempUser); 289 | 290 | tempUser = new User(null, UUID.randomUUID()); 291 | tempUser.setOpenIDIdentifier(verified.get().getIdentifier()); 292 | tempUser.setSessionToken(sessionToken); 293 | 294 | // Extract additional information 295 | if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) { 296 | tempUser.setEmailAddress(extractEmailAddress(authSuccess)); 297 | tempUser.setFirstName(extractFirstName(authSuccess)); 298 | tempUser.setLastName(extractLastName(authSuccess)); 299 | } 300 | 301 | // Provide a basic authority in light of successful authentication 302 | tempUser.getAuthorities().add(Authority.ROLE_PUBLIC); 303 | // see if this is an admin user 304 | if ((OpenIDDemoService.getConfig().getAdminUsers() != null) 305 | && (tempUser.getEmailAddress() != null)) { 306 | Map adminUsers = OpenIDDemoService 307 | .getConfig().getAdminUsers(); 308 | if (adminUsers.containsKey(tempUser.getEmailAddress()) 309 | && (adminUsers.get(tempUser.getEmailAddress()) 310 | .equals("openid"))) { 311 | tempUser.getAuthorities().add(Authority.ROLE_ADMIN); 312 | } 313 | } 314 | 315 | log.info("Extracted a temporary {}", tempUser); 316 | 317 | // Search for a pre-existing User matching the temp User 318 | Optional userOptional = InMemoryUserCache.INSTANCE.getByOpenIDIdentifier(tempUser.getOpenIDIdentifier()); 319 | User user; 320 | if (!userOptional.isPresent()) { 321 | // This is either a new registration or the OpenID identifier has changed 322 | if (tempUser.getEmailAddress() != null) { 323 | userOptional = InMemoryUserCache.INSTANCE.getByEmailAddress(tempUser.getEmailAddress()); 324 | if (!userOptional.isPresent()) { 325 | // This is a new User 326 | log.debug("Registering new {}", tempUser); 327 | user = tempUser; 328 | } else { 329 | // The OpenID identifier has changed so update it 330 | log.debug("Updating OpenID identifier for {}", tempUser); 331 | user = userOptional.get(); 332 | user.setOpenIDIdentifier(tempUser.getOpenIDIdentifier()); 333 | } 334 | 335 | } else { 336 | // No email address to use as backup 337 | log.warn("Rejecting valid authentication. No email address for {}"); 338 | throw new WebApplicationException(Response.Status.UNAUTHORIZED); 339 | } 340 | } else { 341 | // The User has been located by their OpenID identifier 342 | log.debug("Found an existing User using OpenID identifier {}", tempUser); 343 | user = userOptional.get(); 344 | 345 | } 346 | 347 | // Persist the user with the current session token 348 | user.setSessionToken(sessionToken); 349 | InMemoryUserCache.INSTANCE.put(sessionToken, user); 350 | 351 | // Create a suitable view for the response 352 | // The session token has changed so we create the base model directly 353 | BaseModel model = new BaseModel(); 354 | model.setUser(user); 355 | 356 | // Authenticated 357 | View view = new PublicFreemarkerView("private/home.ftl", model); 358 | 359 | // Refresh the session token cookie 360 | return Response 361 | .ok() 362 | .cookie(replaceSessionTokenCookie(Optional.of(user))) 363 | .entity(view) 364 | .build(); 365 | 366 | } else { 367 | log.debug("Failed verification"); 368 | } 369 | } catch (OpenIDException e) { 370 | // present error to the user 371 | log.error("OpenIDException", e); 372 | } 373 | 374 | // Must have failed to be here 375 | throw new WebApplicationException(Response.Status.UNAUTHORIZED); 376 | } 377 | 378 | private String extractEmailAddress(AuthSuccess authSuccess) throws MessageException { 379 | FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension(AxMessage.OPENID_NS_AX); 380 | return getAttributeValue( 381 | fetchResp, 382 | "email", 383 | "", 384 | String.class); 385 | } 386 | 387 | private String extractFirstName(AuthSuccess authSuccess) throws MessageException { 388 | FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension(AxMessage.OPENID_NS_AX); 389 | return getAttributeValue( 390 | fetchResp, 391 | "firstname", 392 | "", 393 | String.class); 394 | } 395 | 396 | private String extractLastName(AuthSuccess authSuccess) throws MessageException { 397 | FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension(AxMessage.OPENID_NS_AX); 398 | return getAttributeValue( 399 | fetchResp, 400 | "lastname", 401 | "", 402 | String.class); 403 | } 404 | 405 | @SuppressWarnings({"unchecked"}) 406 | private T getAttributeValue(FetchResponse fetchResponse, String attribute, T defaultValue, Class clazz) { 407 | List list = fetchResponse.getAttributeValues(attribute); 408 | if (list != null && !list.isEmpty()) { 409 | return (T) list.get(0); 410 | } 411 | 412 | return defaultValue; 413 | 414 | } 415 | 416 | } 417 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/resources/RuntimeExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.resources; 2 | 3 | import com.sun.jersey.api.core.HttpContext; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.ws.rs.WebApplicationException; 8 | import javax.ws.rs.core.Context; 9 | import javax.ws.rs.core.Response; 10 | import javax.ws.rs.ext.ExceptionMapper; 11 | import javax.ws.rs.ext.Provider; 12 | 13 | /** 14 | *

    Provider to provide the following to Jersey framework:

    15 | *
      16 | *
    • Provision of exception to response mapping
    • 17 | *
    18 | * 19 | * @since 0.0.1 20 | *   21 | */ 22 | @Provider 23 | public class RuntimeExceptionMapper implements ExceptionMapper { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(RuntimeExceptionMapper.class); 26 | 27 | @Context 28 | HttpContext httpContext; 29 | 30 | @Override 31 | public Response toResponse(RuntimeException runtime) { 32 | 33 | // Build default response 34 | Response defaultResponse = Response 35 | .serverError() 36 | .entity(new PublicErrorResource().view500()) 37 | .build(); 38 | 39 | // Check for any specific handling 40 | if (runtime instanceof WebApplicationException) { 41 | 42 | return handleWebApplicationException(runtime, defaultResponse); 43 | } 44 | 45 | // Use the default 46 | log.error(runtime.getMessage(),runtime); 47 | return defaultResponse; 48 | 49 | } 50 | 51 | private Response handleWebApplicationException(RuntimeException exception, Response defaultResponse) { 52 | WebApplicationException webAppException = (WebApplicationException) exception; 53 | 54 | // No logging 55 | if (webAppException.getResponse().getStatus() == Response.Status.UNAUTHORIZED.getStatusCode()) { 56 | return Response 57 | .status(Response.Status.UNAUTHORIZED) 58 | .entity(new PublicErrorResource().view401()) 59 | .build(); 60 | } 61 | if (webAppException.getResponse().getStatus() == Response.Status.NOT_FOUND.getStatusCode()) { 62 | return Response 63 | .status(Response.Status.NOT_FOUND) 64 | .entity(new PublicErrorResource().view404()) 65 | .build(); 66 | } 67 | 68 | // Debug logging 69 | 70 | // Warn logging 71 | 72 | // Error logging 73 | log.error(exception.getMessage(),exception); 74 | 75 | return defaultResponse; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/uk/co/froot/demo/openid/views/PublicFreemarkerView.java: -------------------------------------------------------------------------------- 1 | package uk.co.froot.demo.openid.views; 2 | 3 | import com.yammer.dropwizard.views.View; 4 | import uk.co.froot.demo.openid.model.BaseModel; 5 | 6 | /** 7 | *

    View to provide the following to resources:

    8 | *
      9 | *
    • Representation provided by a Freemarker template with a given model
    • 10 | *
    11 | * 12 | * @since 0.0.1 13 | * 14 | */ 15 | public class PublicFreemarkerView extends View { 16 | 17 | private final T model; 18 | 19 | public PublicFreemarkerView(String templateName, T model) { 20 | super("/views/ftl/"+templateName); 21 | this.model = model; 22 | } 23 | 24 | public T getModel() { 25 | return model; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/assets/images/error.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/images/error.jpg -------------------------------------------------------------------------------- /src/main/resources/assets/images/signin-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/images/signin-facebook.png -------------------------------------------------------------------------------- /src/main/resources/assets/images/signin-google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/images/signin-google.png -------------------------------------------------------------------------------- /src/main/resources/assets/images/signin-googleplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/images/signin-googleplus.png -------------------------------------------------------------------------------- /src/main/resources/assets/images/signin-linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/images/signin-linkedin.png -------------------------------------------------------------------------------- /src/main/resources/assets/images/signin-openid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/images/signin-openid.png -------------------------------------------------------------------------------- /src/main/resources/assets/images/signin-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/images/signin-twitter.png -------------------------------------------------------------------------------- /src/main/resources/assets/images/signin-yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/images/signin-yahoo.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/css/openid-shadow.css: -------------------------------------------------------------------------------- 1 | /* 2 | Simple OpenID Plugin 3 | http://code.google.com/p/openid-selector/ 4 | 5 | This code is licensed under the New BSD License. 6 | */ 7 | 8 | #openid_form { 9 | width: 590px; 10 | } 11 | 12 | #openid_form legend { 13 | font-weight: bold; 14 | } 15 | 16 | #openid_choice { 17 | display: none; 18 | } 19 | 20 | #openid_input_area { 21 | clear: both; 22 | padding: 10px; 23 | } 24 | 25 | #openid_btns, #openid_btns br { 26 | clear: both; 27 | } 28 | 29 | #openid_highlight { 30 | padding: 3px; 31 | background-color: #FFFCC9; 32 | float: left; 33 | } 34 | 35 | .openid_large_btn { 36 | width: 100px; 37 | height: 60px; 38 | /* fix for IE 6 only: http://en.wikipedia.org/wiki/CSS_filter#Underscore_hack */ 39 | _width: 104px; 40 | _height: 64px; 41 | 42 | border: 2px solid #DDD; 43 | border-right: 2px solid #ccc; 44 | border-bottom: 2px solid #ccc; 45 | margin: 3px; 46 | float: left; 47 | border-radius: 5px; 48 | -moz-border-radius: 5px; 49 | -webkit-border-radius: 5px; 50 | box-shadow: 2px 2px 4px #ddd; 51 | -moz-box-shadow: 2px 2px 4px #ddd; 52 | -webkit-box-shadow: 2px 2px 4px #ddd; 53 | } 54 | 55 | .openid_large_btn:hover { 56 | margin: 4px 0 0 6px; 57 | border: 2px solid #999; 58 | box-shadow: none; 59 | -moz-box-shadow: none; 60 | -webkit-box-shadow: none; 61 | } 62 | 63 | .openid_small_btn { 64 | width: 24px; 65 | height: 24px; 66 | /* fix for IE 6 only: http://en.wikipedia.org/wiki/CSS_filter#Underscore_hack */ 67 | _width: 28px; 68 | _height: 28px; 69 | 70 | border: 2px solid #DDD; 71 | border-right: 2px solid #ccc; 72 | border-bottom: 2px solid #ccc; 73 | margin: 3px; 74 | float: left; 75 | border-radius: 5px; 76 | -moz-border-radius: 5px; 77 | -webkit-border-radius: 5px; 78 | box-shadow: 2px 2px 4px #ddd; 79 | -moz-box-shadow: 2px 2px 4px #ddd; 80 | -webkit-box-shadow: 2px 2px 4px #ddd; 81 | } 82 | 83 | .openid_small_btn:hover { 84 | margin: 4px 0 0 6px; 85 | border: 2px solid #999; 86 | box-shadow: none; 87 | -moz-box-shadow: none; 88 | -webkit-box-shadow: none; 89 | } 90 | 91 | a.openid_large_btn:focus { 92 | outline: none; 93 | } 94 | 95 | a.openid_large_btn:focus { 96 | -moz-outline-style: none; 97 | } 98 | 99 | .openid_selected { 100 | border: 4px solid #DDD; 101 | } -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/css/openid.css: -------------------------------------------------------------------------------- 1 | /* 2 | Simple OpenID Plugin 3 | http://code.google.com/p/openid-selector/ 4 | 5 | This code is licensed under the New BSD License. 6 | */ 7 | 8 | #openid_form { 9 | width: 580px; 10 | } 11 | 12 | #openid_form legend { 13 | font-weight: bold; 14 | } 15 | 16 | #openid_choice { 17 | display: none; 18 | } 19 | 20 | #openid_input_area { 21 | clear: both; 22 | padding: 10px; 23 | } 24 | 25 | #openid_btns, #openid_btns br { 26 | clear: both; 27 | } 28 | 29 | #openid_highlight { 30 | padding: 3px; 31 | background-color: #FFFCC9; 32 | float: left; 33 | } 34 | 35 | .openid_large_btn { 36 | width: 100px; 37 | height: 60px; 38 | /* fix for IE 6 only: http://en.wikipedia.org/wiki/CSS_filter#Underscore_hack */ 39 | _width: 102px; 40 | _height: 62px; 41 | 42 | border: 1px solid #DDD; 43 | margin: 3px; 44 | float: left; 45 | } 46 | 47 | .openid_small_btn { 48 | width: 24px; 49 | height: 24px; 50 | /* fix for IE 6 only: http://en.wikipedia.org/wiki/CSS_filter#Underscore_hack */ 51 | _width: 26px; 52 | _height: 26px; 53 | 54 | border: 1px solid #DDD; 55 | margin: 3px; 56 | float: left; 57 | } 58 | 59 | a.openid_large_btn:focus { 60 | outline: none; 61 | } 62 | 63 | a.openid_large_btn:focus { 64 | -moz-outline-style: none; 65 | } 66 | 67 | .openid_selected { 68 | border: 4px solid #DDD; 69 | } -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/aol.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/aol.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/facebook.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/facebook.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/google.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/google.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/mailru.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/mailru.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/myopenid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/myopenid.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/openid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/openid.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/rambler.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/rambler.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/verisign.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/verisign.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/vkontakte.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/vkontakte.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/yahoo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/yahoo.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.large/yandex.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.large/yandex.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/aol.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/aol.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/aol.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/aol.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/aol.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/aol.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/blogger.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/blogger.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/blogger.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/blogger.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/blogger.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/blogger.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/claimid.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/claimid.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/claimid.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/claimid.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/claimid.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/claimid.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/clickpass.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/clickpass.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/clickpass.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/clickpass.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/clickpass.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/clickpass.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/facebook.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/facebook.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/facebook.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/facebook.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/facebook.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/facebook.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/flickr.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/flickr.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/flickr.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/flickr.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/flickr.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/flickr.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/google.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/google.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/google.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/google.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/google.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/google.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/google_profile.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/google_profile.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/google_profile.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/google_profile.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/google_profile.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/google_profile.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/launchpad.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/launchpad.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/launchpad.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/launchpad.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/launchpad.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/launchpad.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/linkedin.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/linkedin.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/linkedin.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/linkedin.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/linkedin.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/linkedin.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/livejournal.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/livejournal.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/livejournal.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/livejournal.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/livejournal.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/livejournal.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/mailru.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/mailru.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/mailru.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/mailru.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/mailru.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/mailru.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/myopenid.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/myopenid.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/myopenid.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/myopenid.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/myopenid.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/myopenid.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/openid.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/openid.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/openid.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/openid.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/openid.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/openid.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/rambler.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/rambler.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/rambler.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/rambler.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/rambler.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/rambler.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/technorati.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/technorati.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/technorati.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/technorati.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/technorati.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/technorati.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/twitter.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/twitter.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/twitter.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/twitter.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/twitter.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/twitter.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/verisign.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/verisign.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/verisign.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/verisign.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/verisign.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/verisign.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/vidoop.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/vidoop.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/vidoop.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/vidoop.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/vidoop.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/vidoop.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/vkontakte.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/vkontakte.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/vkontakte.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/vkontakte.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/vkontakte.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/vkontakte.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/winliveid.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/winliveid.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/winliveid.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/winliveid.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/winliveid.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/winliveid.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/wordpress.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/wordpress.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/wordpress.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/wordpress.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/wordpress.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/wordpress.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/yahoo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/yahoo.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/yahoo.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/yahoo.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/yahoo.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/yahoo.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/yandex.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/yandex.ico -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/yandex.ico.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/yandex.ico.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images.small/yandex.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images.small/yandex.ico.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images/openid-inputicon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images/openid-inputicon.gif -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images/openid-providers-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images/openid-providers-en.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/images/openid-providers-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenyee/dropwizard-oauth-openid/34e587e24fb92373ef006a83095a733ed0d516ef/src/main/resources/assets/jquery/plugins/openid-selector/images/openid-providers-ru.png -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/js/openid-de.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simple OpenID Plugin 3 | http://code.google.com/p/openid-selector/ 4 | 5 | This code is licensed under the New BSD License. 6 | */ 7 | 8 | var providers_large = { 9 | google : { 10 | name : 'Google', 11 | url : 'https://www.google.com/accounts/o8/id' 12 | }, 13 | yahoo : { 14 | name : 'Yahoo', 15 | url : 'http://me.yahoo.com/' 16 | }, 17 | aol : { 18 | name : 'AOL', 19 | label : 'Bitte den AOL Benutzernamen eingeben.', 20 | url : 'http://openid.aol.com/{username}' 21 | }, 22 | myopenid : { 23 | name : 'MyOpenID', 24 | label : 'Bitte den MyOpenID Benutzernamen eingeben.', 25 | url : 'http://{username}.myopenid.com/' 26 | }, 27 | openid : { 28 | name : 'OpenID', 29 | label : 'Bitte OpenID eingeben.', 30 | url : null 31 | } 32 | }; 33 | 34 | var providers_small = { 35 | livejournal : { 36 | name : 'LiveJournal', 37 | label : 'Bitte den LiveJournal Benutzernamen eingeben.', 38 | url : 'http://{username}.livejournal.com/' 39 | }, 40 | /* flickr: { 41 | name: 'Flickr', 42 | label: 'Bitte den Flickr Benutzernamen eingeben.', 43 | url: 'http://flickr.com/{username}/' 44 | }, */ 45 | /* technorati: { 46 | name: 'Technorati', 47 | label: 'Bitte den Technorati Benutzernamen eingeben.', 48 | url: 'http://technorati.com/people/technorati/{username}/' 49 | }, */ 50 | wordpress : { 51 | name : 'Wordpress', 52 | label : 'Bitte den Wordpress.com Benutzernamen eingeben.', 53 | url : 'http://{username}.wordpress.com/' 54 | }, 55 | blogger : { 56 | name : 'Blogger', 57 | label : 'Ihr Blogger Konto', 58 | url : 'http://{username}.blogspot.com/' 59 | }, 60 | verisign : { 61 | name : 'Verisign', 62 | label : 'Ihr Verisign Benutzername', 63 | url : 'http://{username}.pip.verisignlabs.com/' 64 | }, 65 | /* vidoop: { 66 | name: 'Vidoop', 67 | label: 'Ihr Vidoop Benutzername', 68 | url: 'http://{username}.myvidoop.com/' 69 | }, */ 70 | /* launchpad: { 71 | name: 'Launchpad', 72 | label: 'Ihr Launchpad Benutzername', 73 | url: 'https://launchpad.net/~{username}' 74 | }, */ 75 | claimid : { 76 | name : 'ClaimID', 77 | label : 'Ihr ClaimID Benutzername', 78 | url : 'http://claimid.com/{username}' 79 | }, 80 | clickpass : { 81 | name : 'ClickPass', 82 | label : 'Bitte den ClickPass Benutzernamen eingeben', 83 | url : 'http://clickpass.com/public/{username}' 84 | }, 85 | google_profile : { 86 | name : 'Google Profile', 87 | label : 'Bitte den Google Profile Benutzernamen eingeben', 88 | url : 'http://www.google.com/profiles/{username}' 89 | } 90 | }; 91 | 92 | openid.locale = 'de'; 93 | openid.sprite = 'en'; // use same sprite as english localization 94 | openid.demo_text = 'Demo Modus. Normalerweise würde die folgende OpenID übermittelt werden:'; 95 | openid.signin_text = 'Anmelden'; 96 | openid.image_title = 'Mit {provider} anmelden'; 97 | -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/js/openid-en.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simple OpenID Plugin 3 | http://code.google.com/p/openid-selector/ 4 | 5 | This code is licensed under the New BSD License. 6 | */ 7 | 8 | var providers_large = { 9 | google : { 10 | name : 'Google', 11 | url : 'https://www.google.com/accounts/o8/id' 12 | }, 13 | yahoo : { 14 | name : 'Yahoo', 15 | url : 'http://me.yahoo.com/' 16 | }, 17 | aol : { 18 | name : 'AOL', 19 | label : 'Enter your AOL screenname.', 20 | url : 'http://openid.aol.com/{username}' 21 | }, 22 | myopenid : { 23 | name : 'MyOpenID', 24 | label : 'Enter your MyOpenID username.', 25 | url : 'http://{username}.myopenid.com/' 26 | }, 27 | openid : { 28 | name : 'OpenID', 29 | label : 'Enter your OpenID.', 30 | url : null 31 | } 32 | }; 33 | 34 | var providers_small = { 35 | livejournal : { 36 | name : 'LiveJournal', 37 | label : 'Enter your Livejournal username.', 38 | url : 'http://{username}.livejournal.com/' 39 | }, 40 | /* flickr: { 41 | name: 'Flickr', 42 | label: 'Enter your Flickr username.', 43 | url: 'http://flickr.com/{username}/' 44 | }, */ 45 | /* technorati: { 46 | name: 'Technorati', 47 | label: 'Enter your Technorati username.', 48 | url: 'http://technorati.com/people/technorati/{username}/' 49 | }, */ 50 | wordpress : { 51 | name : 'Wordpress', 52 | label : 'Enter your Wordpress.com username.', 53 | url : 'http://{username}.wordpress.com/' 54 | }, 55 | blogger : { 56 | name : 'Blogger', 57 | label : 'Your Blogger account', 58 | url : 'http://{username}.blogspot.com/' 59 | }, 60 | verisign : { 61 | name : 'Verisign', 62 | label : 'Your Verisign username', 63 | url : 'http://{username}.pip.verisignlabs.com/' 64 | }, 65 | /* vidoop: { 66 | name: 'Vidoop', 67 | label: 'Your Vidoop username', 68 | url: 'http://{username}.myvidoop.com/' 69 | }, */ 70 | /* launchpad: { 71 | name: 'Launchpad', 72 | label: 'Your Launchpad username', 73 | url: 'https://launchpad.net/~{username}' 74 | }, */ 75 | claimid : { 76 | name : 'ClaimID', 77 | label : 'Your ClaimID username', 78 | url : 'http://claimid.com/{username}' 79 | }, 80 | clickpass : { 81 | name : 'ClickPass', 82 | label : 'Enter your ClickPass username', 83 | url : 'http://clickpass.com/public/{username}' 84 | }, 85 | google_profile : { 86 | name : 'Google Profile', 87 | label : 'Enter your Google Profile username', 88 | url : 'http://www.google.com/profiles/{username}' 89 | } 90 | }; 91 | 92 | openid.locale = 'en'; 93 | openid.sprite = 'en'; // reused in german& japan localization 94 | openid.demo_text = 'In client demo mode. Normally would have submitted OpenID:'; 95 | openid.signin_text = 'Sign-In'; 96 | openid.image_title = 'log in with {provider}'; 97 | -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/js/openid-jp.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simple OpenID Plugin 3 | http://code.google.com/p/openid-selector/ 4 | 5 | This code is licensed under the New BSD License. 6 | */ 7 | 8 | var providers_large = { 9 | google : { 10 | name : 'Google', 11 | url : 'https://www.google.com/accounts/o8/id' 12 | }, 13 | yahoo : { 14 | name : 'Yahoo', 15 | url : 'http://me.yahoo.com/' 16 | }, 17 | aol : { 18 | name : 'AOL', 19 | label : 'AOLのスクリーンネームを記入してください。', 20 | url : 'http://openid.aol.com/{username}' 21 | }, 22 | myopenid : { 23 | name : 'MyOpenID', 24 | label : 'MyOpenIDのユーザーネームを記入してください。', 25 | url : 'http://{username}.myopenid.com/' 26 | }, 27 | openid : { 28 | name : 'OpenID', 29 | label : 'OpenIDを記入してください。', 30 | url : null 31 | } 32 | }; 33 | 34 | var providers_small = { 35 | livejournal : { 36 | name : 'LiveJournal', 37 | label : 'LiveJournalのユーザーネームを記入してください。', 38 | url : 'http://{username}.livejournal.com/' 39 | }, 40 | /* flickr: { 41 | name: 'Flickr', 42 | label: 'Flickrのユーザーネームを記入してください。', 43 | url: 'http://flickr.com/{username}/' 44 | }, */ 45 | /* technorati: { 46 | name: 'Technorati', 47 | label: 'Technoratiのユーザーネームを記入してください。', 48 | url: 'http://technorati.com/people/technorati/{username}/' 49 | }, */ 50 | wordpress : { 51 | name : 'Wordpress', 52 | label : 'Wordpress.comのユーザーネームを記入してください。', 53 | url : 'http://{username}.wordpress.com/' 54 | }, 55 | blogger : { 56 | name : 'Blogger', 57 | label : 'Bloggerのアカウントを記入してください。', 58 | url : 'http://{username}.blogspot.com/' 59 | }, 60 | verisign : { 61 | name : 'Verisign', 62 | label : 'Verisignのユーザーネームを記入してください。', 63 | url : 'http://{username}.pip.verisignlabs.com/' 64 | }, 65 | /* vidoop: { 66 | name: 'Vidoop', 67 | label: 'Vidoopのユーザーネームを記入してください。', 68 | url: 'http://{username}.myvidoop.com/' 69 | }, */ 70 | /* launchpad: { 71 | name: 'Launchpad', 72 | label: 'Launchpadのユーザーネームを記入してください。', 73 | url: 'https://launchpad.net/~{username}' 74 | }, */ 75 | claimid : { 76 | name : 'ClaimID', 77 | label : 'ClaimIDのユーザーネームを記入してください。', 78 | url : 'http://claimid.com/{username}' 79 | }, 80 | clickpass : { 81 | name : 'ClickPass', 82 | label : 'ClickPassのユーザーネームを記入してください。', 83 | url : 'http://clickpass.com/public/{username}' 84 | }, 85 | google_profile : { 86 | name : 'Google Profile', 87 | label : 'Google Profileのユーザーネームを記入してください。', 88 | url : 'http://www.google.com/profiles/{username}' 89 | } 90 | }; 91 | 92 | openid.locale = 'jp'; 93 | openid.sprite = 'en'; // use same sprite as english localization 94 | openid.demo_text = '今クライアントデモモードになっています。普通は次のOpenIDを出さなければいけません:'; 95 | openid.signin_text = 'ログイン'; 96 | openid.image_title = '{provider}でログイン'; -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/js/openid-jquery.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simple OpenID Plugin 3 | http://code.google.com/p/openid-selector/ 4 | 5 | This code is licensed under the New BSD License. 6 | */ 7 | 8 | var providers; 9 | var openid; 10 | (function ($) { 11 | openid = { 12 | version : '1.3', // version constant 13 | demo : false, 14 | demo_text : null, 15 | cookie_expires : 6 * 30, // 6 months. 16 | cookie_name : 'openid_provider', 17 | cookie_path : '/', 18 | 19 | img_path : '/jquery/plugins/openid-selector/images/', 20 | locale : null, // is set in openid-.js 21 | sprite : null, // usually equals to locale, is set in 22 | // openid-.js 23 | signin_text : null, // text on submit button on the form 24 | all_small : false, // output large providers w/ small icons 25 | no_sprite : false, // don't use sprite image 26 | image_title : '{provider}', // for image title 27 | 28 | input_id : null, 29 | provider_url : null, 30 | provider_id : null, 31 | 32 | /** 33 | * Class constructor 34 | * 35 | * @return {Void} 36 | */ 37 | init : function(input_id) { 38 | providers = $.extend({}, providers_large, providers_small); 39 | var openid_btns = $('#openid_btns'); 40 | this.input_id = input_id; 41 | $('#openid_choice').show(); 42 | $('#openid_input_area').empty(); 43 | var i = 0; 44 | // add box for each provider 45 | for (id in providers_large) { 46 | box = this.getBoxHTML(id, providers_large[id], (this.all_small ? 'small' : 'large'), i++); 47 | openid_btns.append(box); 48 | } 49 | if (providers_small) { 50 | openid_btns.append('
    '); 51 | for (id in providers_small) { 52 | box = this.getBoxHTML(id, providers_small[id], 'small', i++); 53 | openid_btns.append(box); 54 | } 55 | } 56 | $('#openid_form').submit(this.submit); 57 | var box_id = this.readCookie(); 58 | if (box_id) { 59 | this.signin(box_id, true); 60 | } 61 | }, 62 | 63 | /** 64 | * @return {String} 65 | */ 66 | getBoxHTML : function(box_id, provider, box_size, index) { 67 | if (this.no_sprite) { 68 | var image_ext = box_size == 'small' ? '.ico.gif' : '.gif'; 69 | return ''; 72 | } 73 | var x = box_size == 'small' ? -index * 24 : -index * 100; 74 | var y = box_size == 'small' ? -60 : 0; 75 | return ''; 78 | }, 79 | 80 | /** 81 | * Provider image click 82 | * 83 | * @return {Void} 84 | */ 85 | signin : function(box_id, onload) { 86 | var provider = providers[box_id]; 87 | if (!provider) { 88 | return; 89 | } 90 | this.highlight(box_id); 91 | this.setCookie(box_id); 92 | this.provider_id = box_id; 93 | this.provider_url = provider['url']; 94 | // prompt user for input? 95 | if (provider['label']) { 96 | this.useInputBox(provider); 97 | } else { 98 | $('#openid_input_area').empty(); 99 | if (!onload) { 100 | $('#openid_form').submit(); 101 | } 102 | } 103 | }, 104 | 105 | /** 106 | * Sign-in button click 107 | * 108 | * @return {Boolean} 109 | */ 110 | submit : function() { 111 | var url = openid.provider_url; 112 | if (url) { 113 | url = url.replace('{username}', $('#openid_username').val()); 114 | openid.setOpenIdUrl(url); 115 | } 116 | if (openid.demo) { 117 | alert(openid.demo_text + "\r\n" + document.getElementById(openid.input_id).value); 118 | return false; 119 | } 120 | if (url.indexOf("javascript:") == 0) { 121 | url = url.substr("javascript:".length); 122 | eval(url); 123 | return false; 124 | } 125 | return true; 126 | }, 127 | 128 | /** 129 | * @return {Void} 130 | */ 131 | setOpenIdUrl : function(url) { 132 | var hidden = document.getElementById(this.input_id); 133 | if (hidden != null) { 134 | hidden.value = url; 135 | } else { 136 | $('#openid_form').append(''); 137 | } 138 | }, 139 | 140 | /** 141 | * @return {Void} 142 | */ 143 | highlight : function(box_id) { 144 | // remove previous highlight. 145 | var highlight = $('#openid_highlight'); 146 | if (highlight) { 147 | highlight.replaceWith($('#openid_highlight a')[0]); 148 | } 149 | // add new highlight. 150 | $('.' + box_id).wrap('
    '); 151 | }, 152 | 153 | setCookie : function(value) { 154 | var date = new Date(); 155 | date.setTime(date.getTime() + (this.cookie_expires * 24 * 60 * 60 * 1000)); 156 | var expires = "; expires=" + date.toGMTString(); 157 | document.cookie = this.cookie_name + "=" + value + expires + "; path=" + this.cookie_path; 158 | }, 159 | 160 | readCookie : function() { 161 | var nameEQ = this.cookie_name + "="; 162 | var ca = document.cookie.split(';'); 163 | for ( var i = 0; i < ca.length; i++) { 164 | var c = ca[i]; 165 | while (c.charAt(0) == ' ') 166 | c = c.substring(1, c.length); 167 | if (c.indexOf(nameEQ) == 0) 168 | return c.substring(nameEQ.length, c.length); 169 | } 170 | return null; 171 | }, 172 | 173 | /** 174 | * @return {Void} 175 | */ 176 | useInputBox : function(provider) { 177 | var input_area = $('#openid_input_area'); 178 | var html = ''; 179 | var id = 'openid_username'; 180 | var value = ''; 181 | var label = provider['label']; 182 | var style = ''; 183 | if (label) { 184 | html = '

    ' + label + '

    '; 185 | } 186 | if (provider['name'] == 'OpenID') { 187 | id = this.input_id; 188 | value = 'http://'; 189 | style = 'background: #FFF url(' + this.img_path + 'openid-inputicon.gif) no-repeat scroll 0 50%; padding-left:18px;'; 190 | } 191 | html += '' 192 | + ''; 193 | input_area.empty(); 194 | input_area.append(html); 195 | $('#' + id).focus(); 196 | }, 197 | 198 | setDemoMode : function(demoMode) { 199 | this.demo = demoMode; 200 | } 201 | }; 202 | })(jQuery); 203 | -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/js/openid-ru.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simple OpenID Plugin 3 | http://code.google.com/p/openid-selector/ 4 | 5 | This code is licensed under the New BSD License. 6 | */ 7 | 8 | var providers_large = { 9 | yandex : { 10 | name : 'Яндекс', 11 | url : 'http://openid.yandex.ru' 12 | }, 13 | rambler : { 14 | name : 'Рамблер', 15 | url : 'http://www.rambler.ru' 16 | }, 17 | google : { 18 | name : 'Google', 19 | url : 'https://www.google.com/accounts/o8/id' 20 | }, 21 | yahoo : { 22 | name : 'Yahoo', 23 | url : 'http://me.yahoo.com/' 24 | }, 25 | myopenid : { 26 | name : 'MyOpenID', 27 | label : 'Введите ваше имя пользователя на MyOpenID.', 28 | url : 'http://{username}.myopenid.com/' 29 | } 30 | }; 31 | 32 | var providers_small = { 33 | openid : { 34 | name : 'OpenID', 35 | label : 'Введите ваш OpenID.', 36 | url : null 37 | }, 38 | livejournal : { 39 | name : 'Живой Журнал', 40 | label : 'Введите ваше имя в Живом Журнале.', 41 | url : 'http://{username}.livejournal.com/' 42 | }, 43 | flickr : { 44 | name : 'Flickr', 45 | label : 'Введите ваше имя на Flickr.', 46 | url : 'http://flickr.com/{username}/' 47 | }, 48 | wordpress : { 49 | name : 'Wordpress', 50 | label : 'Введите ваше имя на Wordpress.com.', 51 | url : 'http://{username}.wordpress.com/' 52 | }, 53 | blogger : { 54 | name : 'Blogger', 55 | label : 'Ваш Blogger аккаунт', 56 | url : 'http://{username}.blogspot.com/' 57 | }, 58 | verisign : { 59 | name : 'Verisign', 60 | label : 'Ваше имя пользователя на Verisign', 61 | url : 'http://{username}.pip.verisignlabs.com/' 62 | }, 63 | google_profile : { 64 | name : 'Профиль Google', 65 | label : 'Введите ваше имя на Google Profile', 66 | url : 'http://www.google.com/profiles/{username}' 67 | } 68 | }; 69 | 70 | openid.locale = 'ru'; 71 | openid.sprite = 'ru'; // reused in ukrainian localization 72 | openid.demo_text = 'В демонстрационном режиме на клиенте. В действительности произошел бы сабмит следующего OpenID:'; 73 | openid.signin_text = 'Войти'; 74 | openid.image_title = 'войти c {provider}'; 75 | -------------------------------------------------------------------------------- /src/main/resources/assets/jquery/plugins/openid-selector/js/openid-uk.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simple OpenID Plugin 3 | http://code.google.com/p/openid-selector/ 4 | 5 | This code is licensed under the New BSD License. 6 | */ 7 | 8 | var providers_large = { 9 | yandex : { 10 | name : 'Яндекс', 11 | url : 'http://openid.yandex.ru' 12 | }, 13 | rambler : { 14 | name : 'Рамблер', 15 | url : 'http://www.rambler.ru' 16 | }, 17 | google : { 18 | name : 'Google', 19 | url : 'https://www.google.com/accounts/o8/id' 20 | }, 21 | yahoo : { 22 | name : 'Yahoo', 23 | url : 'http://me.yahoo.com/' 24 | }, 25 | myopenid : { 26 | name : 'MyOpenID', 27 | label : 'Введіть ваше ім\'я користувача на MyOpenID.', 28 | url : 'http://{username}.myopenid.com/' 29 | } 30 | }; 31 | 32 | var providers_small = { 33 | openid : { 34 | name : 'OpenID', 35 | label : 'Введіть ваш OpenID.', 36 | url : null 37 | }, 38 | livejournal : { 39 | name : 'Живий Журнал', 40 | label : 'Введіть ваше ім\'я в Живому Журналі.', 41 | url : 'http://{username}.livejournal.com/' 42 | }, 43 | flickr : { 44 | name : 'Flickr', 45 | label : 'Введіть ваше ім\'я на Flickr.', 46 | url : 'http://flickr.com/{username}/' 47 | }, 48 | wordpress : { 49 | name : 'Wordpress', 50 | label : 'Введіть ваше ім\'я на Wordpress.com.', 51 | url : 'http://{username}.wordpress.com/' 52 | }, 53 | blogger : { 54 | name : 'Blogger', 55 | label : 'Ваш Blogger аккаунт', 56 | url : 'http://{username}.blogspot.com/' 57 | }, 58 | verisign : { 59 | name : 'Verisign', 60 | label : 'Ваше ім\'я користувача на Verisign', 61 | url : 'http://{username}.pip.verisignlabs.com/' 62 | }, 63 | google_profile : { 64 | name : 'Профіль Google', 65 | label : 'Введіть ваше ім\'я на Google Profile', 66 | url : 'http://www.google.com/profiles/{username}' 67 | } 68 | }; 69 | 70 | openid.locale = 'uk'; 71 | openid.sprite = 'ru'; // use same sprite as russian localization 72 | openid.demo_text = 'В демонстраційному режимі на клієнті. Насправді пройшов би сабміт наступного OpenID:'; 73 | openid.signin_text = 'Увійти'; 74 | openid.image_title = 'увійти з {provider}'; 75 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | OAuth/OpenID Demo - Powered by Dropwizard 2 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/common/home.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="" type="uk.co.froot.demo.openid.views.PublicFreemarkerView" --> 2 | 3 | 4 | 5 | <#include "../includes/common/head.ftl"> 6 | 7 | 8 | 9 |
    10 | <#include "../includes/common/header.ftl"> 11 | 12 |

    Public home page

    13 | 14 |

    Anyone can access this page

    15 | 16 |

    Access protected info. This is available to anyone after authentication

    17 | 18 |

    Access private info. This is only available to people who authenticate with the 19 | specific email address set in app.yml as adminUsers.

    20 | 21 |
    22 | 23 |

    Show Markdown demo text. This is available to anyone.

    24 | 25 | <#include "../includes/common/footer.ftl"> 26 | 27 |
    28 | 29 | <#include "../includes/common/cdn-scripts.ftl"> 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/common/login.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="" type="uk.co.froot.demo.openid.views.PublicFreemarkerView" --> 2 | 3 | 4 | 5 | <#include "../includes/common/head.ftl"> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 | <#include "../includes/common/header.ftl"> 15 | 16 |

    JQuery Simple OpenID Selector Demo

    17 | 18 |

    This is a simple example to show how you can include the Javascript into your page.

    19 | 20 |

    This example is based on the openid-selector project that 21 | also supports Prototype and Mootools.

    22 |
    23 | 24 |
    25 |
    26 | Sign-in or Create New Account 27 |
    28 |

    Please click your account provider:

    29 | 30 |
    31 |
    32 |
    33 | 34 | 35 |
    36 | 41 |
    42 |
    43 | 44 | 45 | 46 |

    OAuth Login Buttons

    47 | 48 |

    These buttons log you in via 3rd party OAuth services.

    49 | 50 |

    We need to request the email scopes from each service so we can use that 51 | for authorization. But we can also request access to other stuff like 52 | your feeds or photos.

    53 | 54 |
    55 |
    56 | <#list model.OAuthCfg as x> 57 |
    58 | 59 |
    60 |
    61 | 62 | 63 |
    64 | 65 | <#include "../includes/common/footer.ftl"> 66 | 67 | 68 | 69 | <#include "../includes/common/cdn-scripts.ftl"> 70 | 71 | 72 | 73 | 74 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/common/markdown.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="" type="uk.co.froot.demo.openid.views.PublicFreemarkerView" --> 2 | 3 | 4 | 5 | <#include "../includes/common/head.ftl"> 6 | 7 | 8 | 9 |
    10 | <#include "../includes/common/header.ftl"> 11 | 12 | 13 | ${model.markdownHtml} 14 | 15 | <#include "../includes/common/footer.ftl"> 16 | 17 |
    18 | 19 | <#include "../includes/common/cdn-scripts.ftl"> 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/error/401.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <#include "../includes/common/head.ftl"> 5 | 6 | 7 | 8 | 9 |
    10 | <#include "../includes/common/header.ftl"> 11 | 12 |

    Access denied!

    13 | 14 |

    You may need to log in to access this information

    15 | 16 |

    You can return to your private home page

    17 | 18 | <#include "../includes/common/footer.ftl"> 19 | 20 |
    21 | 22 | 23 | <#include "../includes/common/cdn-scripts.ftl"> 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/error/404.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <#include "../includes/common/head.ftl"> 5 | 6 | 7 | 8 | 9 |
    10 | <#include "../includes/common/header.ftl"> 11 |
    12 | 13 |
    14 |

    There doesn't appear to be anything here

    15 | 16 | 17 |

    It looks as though you were following a broken link. If it came from our site, we're very sorry about 18 | this and our link maintenance reporting tool will have picked this up and 19 | sent a notification to the support engineers about it. They will sort it out as soon as possible.

    20 | 21 |

    Since the information isn't here, you should return to the home page.

    22 | 23 |

    Technical

    24 |

    404 - Resource not found

    25 | 26 |
    27 | 28 |
    29 | 30 |
    31 | 32 |
    33 | 34 |
    35 | 36 |
    37 | 38 | <#include "../includes/common/footer.ftl"> 39 | 40 |
    41 | 42 | 43 | <#include "../includes/common/cdn-scripts.ftl"> 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/error/500.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <#include "../includes/common/head.ftl"> 5 | 6 | 7 | 8 | 9 |
    10 | <#include "../includes/common/header.ftl"> 11 |
    12 | 13 |
    14 |

    Oh, snap!

    15 |

    It looks as though something has broken on our system. Our internal error reporting system has picked this up 16 | and has sent notification to the support engineers about it. They will sort it out as soon as possible.

    17 | 18 |

    We are very sorry to cause you this inconvenience, your time is precious and we screwed up.

    19 | 20 |

    Since there isn't much else to done, you should return to the home page.

    21 | 22 |

    Technical

    23 |

    500 - Internal server error

    24 | 25 |
    26 |
    27 | 28 |
    29 | 30 |
    31 | 32 |
    33 | 34 |
    35 | 36 | <#include "../includes/common/footer.ftl"> 37 | 38 |
    39 | 40 | 41 | <#include "../includes/common/cdn-scripts.ftl"> 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/includes/common/cdn-scripts.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/includes/common/footer.ftl: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/includes/common/head.ftl: -------------------------------------------------------------------------------- 1 | 2 | OpenId/OAuth Demonstrator 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/includes/common/header.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="" type="uk.co.froot.demo.dojo.views.PublicFreemarkerView" --> 2 | 3 |
    4 |
    5 | 13 |

    DropWizard OpenID-OAuth Sample

    14 |
    15 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/includes/common/left-sidebar.ftl: -------------------------------------------------------------------------------- 1 |
    Left sidebar
    -------------------------------------------------------------------------------- /src/main/resources/views/ftl/includes/common/right-sidebar.ftl: -------------------------------------------------------------------------------- 1 |
    Right sidebar
    -------------------------------------------------------------------------------- /src/main/resources/views/ftl/private/admin.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="" type="uk.co.froot.demo.openid.views.PublicFreemarkerView" --> 2 | 3 | 4 | 5 | <#include "../includes/common/head.ftl"> 6 | 7 | 8 | 9 |
    10 | <#include "../includes/common/header.ftl"> 11 | 12 |

    Admin data

    13 |

    This can only be seen by administrators

    14 | 15 | <#include "../includes/common/footer.ftl"> 16 | 17 |
    18 | 19 | <#include "../includes/common/cdn-scripts.ftl"> 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/views/ftl/private/home.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="" type="uk.co.froot.demo.openid.views.PublicFreemarkerView" --> 2 | 3 | 4 | 5 | <#include "../includes/common/head.ftl"> 6 | 7 | 8 | 9 |
    10 | <#include "../includes/common/header.ftl"> 11 | 12 |
    13 |

    Your private data

    14 |

    Congratulations! You authenticated through OpenId or OAuth

    15 |

    This can be seen by administrators and authenticated public

    16 |

    Try to get to the admin page

    17 |
    18 | 19 | <#if model.contacts??> 20 |
    21 |

    Social Contacts

    22 | <#list model.contacts as c> 23 |
    24 |
    25 | <#if c.profileImageURL??> 26 | ${c.displayName} 28 | 29 | ${c.displayName} 30 |
    31 |
    32 | 33 |
    34 | 35 | 36 | <#if model.albums??> 37 |
    38 |

    Albums

    39 | <#list model.albums as a> 40 | 56 | 57 |
    58 | 59 | 60 | <#include "../includes/common/footer.ftl"> 61 | 62 |
    63 | 64 | <#include "../includes/common/cdn-scripts.ftl"> 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/resources/views/markdown/demo-all-elements.md: -------------------------------------------------------------------------------- 1 | Markdown: Syntax 2 | ================ 3 | 4 | This text is lifted from [Daring Fireballs's syntax page](http://daringfireball.net/projects/markdown/syntax) to promote the use of Markdown. 5 | 6 | 13 | 14 | * [Overview](#overview) 15 | * [Philosophy](#philosophy) 16 | * [Inline HTML (tables and divs)](#html) 17 | * [Automatic Escaping for Special Characters](#autoescape) 18 | * [Block Elements](#block) 19 | * [Paragraphs and Line Breaks](#p) 20 | * [Headers](#header) 21 | * [Blockquotes](#blockquote) 22 | * [Lists](#list) 23 | * [Code Blocks](#precode) 24 | * [Horizontal Rules](#hr) 25 | * [Span Elements](#span) 26 | * [Links](#link) 27 | * [Emphasis](#em) 28 | * [Code](#code) 29 | * [Images](#img) 30 | * [Miscellaneous](#misc) 31 | * [Backslash Escapes](#backslash) 32 | * [Automatic Links](#autolink) 33 | 34 | 35 | **Note:** This document is itself written using Markdown; you 36 | can [see the source for it by adding '.text' to the URL][src]. 37 | 38 | [src]: /projects/markdown/syntax.text 39 | 40 | * * * 41 | 42 |

    Overview

    43 | 44 |

    Philosophy

    45 | 46 | Markdown is intended to be as easy-to-read and easy-to-write as is feasible. 47 | 48 | Readability, however, is emphasized above all else. A Markdown-formatted 49 | document should be publishable as-is, as plain text, without looking 50 | like it's been marked up with tags or formatting instructions. While 51 | Markdown's syntax has been influenced by several existing text-to-HTML 52 | filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4], 53 | [Grutatext] [5], and [EtText] [6] -- the single biggest source of 54 | inspiration for Markdown's syntax is the format of plain text email. 55 | 56 | [1]: http://docutils.sourceforge.net/mirror/setext.html 57 | [2]: http://www.aaronsw.com/2002/atx/ 58 | [3]: http://textism.com/tools/textile/ 59 | [4]: http://docutils.sourceforge.net/rst.html 60 | [5]: http://www.triptico.com/software/grutatxt.html 61 | [6]: http://ettext.taint.org/doc/ 62 | 63 | To this end, Markdown's syntax is comprised entirely of punctuation 64 | characters, which punctuation characters have been carefully chosen so 65 | as to look like what they mean. E.g., asterisks around a word actually 66 | look like \*emphasis\*. Markdown lists look like, well, lists. Even 67 | blockquotes look like quoted passages of text, assuming you've ever 68 | used email. 69 | 70 | 71 | 72 |

    Inline HTML (tables, divs etc)

    73 | 74 | Markdown's syntax is intended for one purpose: to be used as a 75 | format for *writing* for the web. 76 | 77 | Markdown is not a replacement for HTML, or even close to it. Its 78 | syntax is very small, corresponding only to a very small subset of 79 | HTML tags. The idea is *not* to create a syntax that makes it easier 80 | to insert HTML tags. In my opinion, HTML tags are already easy to 81 | insert. The idea for Markdown is to make it easy to read, write, and 82 | edit prose. HTML is a *publishing* format; Markdown is a *writing* 83 | format. Thus, Markdown's formatting syntax only addresses issues that 84 | can be conveyed in plain text. 85 | 86 | For any markup that is not covered by Markdown's syntax, you simply 87 | use HTML itself. There's no need to preface it or delimit it to 88 | indicate that you're switching from Markdown to HTML; you just use 89 | the tags. 90 | 91 | The only restrictions are that block-level HTML elements -- e.g. `
    `, 92 | ``, `
    `, `

    `, etc. -- must be separated from surrounding 93 | content by blank lines, and the start and end tags of the block should 94 | not be indented with tabs or spaces. Markdown is smart enough not 95 | to add extra (unwanted) `

    ` tags around HTML block-level tags. 96 | 97 | For example, to add an HTML table to a Markdown article: 98 | 99 | This is a regular paragraph. 100 | 101 |

    102 | 103 | 104 | 105 | 106 |
    FooBar
    107 | 108 | This is another regular paragraph. 109 | 110 | Rendered: 111 | 112 | 113 | 114 | 115 | 116 |
    FooBar
    117 | 118 | Note that Markdown formatting syntax is not processed within block-level 119 | HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an 120 | HTML block. 121 | 122 | Span-level HTML tags -- e.g. ``, ``, or `` -- can be 123 | used anywhere in a Markdown paragraph, list item, or header. If you 124 | want, you can even use HTML tags instead of Markdown formatting; e.g. if 125 | you'd prefer to use HTML `` or `` tags instead of Markdown's 126 | link or image syntax, go right ahead. 127 | 128 | Unlike block-level HTML tags, Markdown syntax *is* processed within 129 | span-level tags. 130 | 131 | 132 |

    Automatic Escaping for Special Characters

    133 | 134 | In HTML, there are two characters that demand special treatment: `<` 135 | and `&`. Left angle brackets are used to start tags; ampersands are 136 | used to denote HTML entities. If you want to use them as literal 137 | characters, you must escape them as entities, e.g. `<`, and 138 | `&`. 139 | 140 | Ampersands in particular are bedeviling for web writers. If you want to 141 | write about 'AT&T', you need to write '`AT&T`'. You even need to 142 | escape ampersands within URLs. Thus, if you want to link to: 143 | 144 | http://images.google.com/images?num=30&q=larry+bird 145 | 146 | you need to encode the URL as: 147 | 148 | http://images.google.com/images?num=30&q=larry+bird 149 | 150 | in your anchor tag `href` attribute. Needless to say, this is easy to 151 | forget, and is probably the single most common source of HTML validation 152 | errors in otherwise well-marked-up web sites. 153 | 154 | Markdown allows you to use these characters naturally, taking care of 155 | all the necessary escaping for you. If you use an ampersand as part of 156 | an HTML entity, it remains unchanged; otherwise it will be translated 157 | into `&`. 158 | 159 | So, if you want to include a copyright symbol in your article, you can write: 160 | 161 | © 162 | 163 | and Markdown will leave it alone. But if you write: 164 | 165 | AT&T 166 | 167 | Markdown will translate it to: 168 | 169 | AT&T 170 | 171 | Similarly, because Markdown supports [inline HTML](#html), if you use 172 | angle brackets as delimiters for HTML tags, Markdown will treat them as 173 | such. But if you write: 174 | 175 | 4 < 5 176 | 177 | Markdown will translate it to: 178 | 179 | 4 < 5 180 | 181 | However, inside Markdown code spans and blocks, angle brackets and 182 | ampersands are *always* encoded automatically. This makes it easy to use 183 | Markdown to write about HTML code. (As opposed to raw HTML, which is a 184 | terrible format for writing about HTML syntax, because every single `<` 185 | and `&` in your example code needs to be escaped.) 186 | 187 | 188 | * * * 189 | 190 | 191 |

    Block Elements

    192 | 193 | 194 |

    Paragraphs and Line Breaks

    195 | 196 | A paragraph is simply one or more consecutive lines of text, separated 197 | by one or more blank lines. (A blank line is any line that looks like a 198 | blank line -- a line containing nothing but spaces or tabs is considered 199 | blank.) Normal paragraphs should not be indented with spaces or tabs. 200 | 201 | The implication of the "one or more consecutive lines of text" rule is 202 | that Markdown supports "hard-wrapped" text paragraphs. This differs 203 | significantly from most other text-to-HTML formatters (including Movable 204 | Type's "Convert Line Breaks" option) which translate every line break 205 | character in a paragraph into a `
    ` tag. 206 | 207 | When you *do* want to insert a `
    ` break tag using Markdown, you 208 | end a line with two or more spaces, then type return. 209 | 210 | Yes, this takes a tad more effort to create a `
    `, but a simplistic 211 | "every line break is a `
    `" rule wouldn't work for Markdown. 212 | Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l] 213 | work best -- and look better -- when you format them with hard breaks. 214 | 215 | [bq]: #blockquote 216 | [l]: #list 217 | 218 | 219 | 220 | 221 | 222 | Markdown supports two styles of headers, [Setext] [1] and [atx] [2]. 223 | 224 | Setext-style headers are "underlined" using equal signs (for first-level 225 | headers) and dashes (for second-level headers). For example: 226 | 227 | This is an H1 228 | ============= 229 | 230 | This is an H2 231 | ------------- 232 | 233 | Any number of underlining `=`'s or `-`'s will work. 234 | 235 | Atx-style headers use 1-6 hash characters at the start of the line, 236 | corresponding to header levels 1-6. For example: 237 | 238 | # This is an H1 239 | 240 | ## This is an H2 241 | 242 | ###### This is an H6 243 | 244 | Optionally, you may "close" atx-style headers. This is purely 245 | cosmetic -- you can use this if you think it looks better. The 246 | closing hashes don't even need to match the number of hashes 247 | used to open the header. (The number of opening hashes 248 | determines the header level.) : 249 | 250 | # This is an H1 # 251 | 252 | ## This is an H2 ## 253 | 254 | ### This is an H3 ###### 255 | 256 | 257 |

    Blockquotes

    258 | 259 | Markdown uses email-style `>` characters for blockquoting. If you're 260 | familiar with quoting passages of text in an email message, then you 261 | know how to create a blockquote in Markdown. It looks best if you hard 262 | wrap the text and put a `>` before every line: 263 | 264 | > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, 265 | > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. 266 | > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 267 | > 268 | > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 269 | > id sem consectetuer libero luctus adipiscing. 270 | 271 | Markdown allows you to be lazy and only put the `>` before the first 272 | line of a hard-wrapped paragraph: 273 | 274 | > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, 275 | consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. 276 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 277 | 278 | > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 279 | id sem consectetuer libero luctus adipiscing. 280 | 281 | Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by 282 | adding additional levels of `>`: 283 | 284 | > This is the first level of quoting. 285 | > 286 | > > This is nested blockquote. 287 | > 288 | > Back to the first level. 289 | 290 | Blockquotes can contain other Markdown elements, including headers, lists, 291 | and code blocks: 292 | 293 | > ## This is a header. 294 | > 295 | > 1. This is the first list item. 296 | > 2. This is the second list item. 297 | > 298 | > Here's some example code: 299 | > 300 | > return shell_exec("echo $input | $markdown_script"); 301 | 302 | Any decent text editor should make email-style quoting easy. For 303 | example, with BBEdit, you can make a selection and choose Increase 304 | Quote Level from the Text menu. 305 | 306 | 307 |

    Lists

    308 | 309 | Markdown supports ordered (numbered) and unordered (bulleted) lists. 310 | 311 | Unordered lists use asterisks, pluses, and hyphens -- interchangably 312 | -- as list markers: 313 | 314 | * Red 315 | * Green 316 | * Blue 317 | 318 | is equivalent to: 319 | 320 | + Red 321 | + Green 322 | + Blue 323 | 324 | and: 325 | 326 | - Red 327 | - Green 328 | - Blue 329 | 330 | Ordered lists use numbers followed by periods: 331 | 332 | 1. Bird 333 | 2. McHale 334 | 3. Parish 335 | 336 | It's important to note that the actual numbers you use to mark the 337 | list have no effect on the HTML output Markdown produces. The HTML 338 | Markdown produces from the above list is: 339 | 340 |
      341 |
    1. Bird
    2. 342 |
    3. McHale
    4. 343 |
    5. Parish
    6. 344 |
    345 | 346 | If you instead wrote the list in Markdown like this: 347 | 348 | 1. Bird 349 | 1. McHale 350 | 1. Parish 351 | 352 | or even: 353 | 354 | 3. Bird 355 | 1. McHale 356 | 8. Parish 357 | 358 | you'd get the exact same HTML output. The point is, if you want to, 359 | you can use ordinal numbers in your ordered Markdown lists, so that 360 | the numbers in your source match the numbers in your published HTML. 361 | But if you want to be lazy, you don't have to. 362 | 363 | If you do use lazy list numbering, however, you should still start the 364 | list with the number 1. At some point in the future, Markdown may support 365 | starting ordered lists at an arbitrary number. 366 | 367 | List markers typically start at the left margin, but may be indented by 368 | up to three spaces. List markers must be followed by one or more spaces 369 | or a tab. 370 | 371 | To make lists look nice, you can wrap items with hanging indents: 372 | 373 | * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 374 | Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, 375 | viverra nec, fringilla in, laoreet vitae, risus. 376 | * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 377 | Suspendisse id sem consectetuer libero luctus adipiscing. 378 | 379 | But if you want to be lazy, you don't have to: 380 | 381 | * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 382 | Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, 383 | viverra nec, fringilla in, laoreet vitae, risus. 384 | * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 385 | Suspendisse id sem consectetuer libero luctus adipiscing. 386 | 387 | If list items are separated by blank lines, Markdown will wrap the 388 | items in `

    ` tags in the HTML output. For example, this input: 389 | 390 | * Bird 391 | * Magic 392 | 393 | will turn into: 394 | 395 |

      396 |
    • Bird
    • 397 |
    • Magic
    • 398 |
    399 | 400 | But this: 401 | 402 | * Bird 403 | 404 | * Magic 405 | 406 | will turn into: 407 | 408 |
      409 |
    • Bird

    • 410 |
    • Magic

    • 411 |
    412 | 413 | List items may consist of multiple paragraphs. Each subsequent 414 | paragraph in a list item must be indented by either 4 spaces 415 | or one tab: 416 | 417 | 1. This is a list item with two paragraphs. Lorem ipsum dolor 418 | sit amet, consectetuer adipiscing elit. Aliquam hendrerit 419 | mi posuere lectus. 420 | 421 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet 422 | vitae, risus. Donec sit amet nisl. Aliquam semper ipsum 423 | sit amet velit. 424 | 425 | 2. Suspendisse id sem consectetuer libero luctus adipiscing. 426 | 427 | It looks nice if you indent every line of the subsequent 428 | paragraphs, but here again, Markdown will allow you to be 429 | lazy: 430 | 431 | * This is a list item with two paragraphs. 432 | 433 | This is the second paragraph in the list item. You're 434 | only required to indent the first line. Lorem ipsum dolor 435 | sit amet, consectetuer adipiscing elit. 436 | 437 | * Another item in the same list. 438 | 439 | To put a blockquote within a list item, the blockquote's `>` 440 | delimiters need to be indented: 441 | 442 | * A list item with a blockquote: 443 | 444 | > This is a blockquote 445 | > inside a list item. 446 | 447 | To put a code block within a list item, the code block needs 448 | to be indented *twice* -- 8 spaces or two tabs: 449 | 450 | * A list item with a code block: 451 | 452 | 453 | 454 | 455 | It's worth noting that it's possible to trigger an ordered list by 456 | accident, by writing something like this: 457 | 458 | 1986. What a great season. 459 | 460 | In other words, a *number-period-space* sequence at the beginning of a 461 | line. To avoid this, you can backslash-escape the period: 462 | 463 | 1986\. What a great season. 464 | 465 | 466 | 467 |

    Code Blocks

    468 | 469 | Pre-formatted code blocks are used for writing about programming or 470 | markup source code. Rather than forming normal paragraphs, the lines 471 | of a code block are interpreted literally. Markdown wraps a code block 472 | in both `
    ` and `` tags.
    473 | 
    474 | To produce a code block in Markdown, simply indent every line of the
    475 | block by at least 4 spaces or 1 tab. For example, given this input:
    476 | 
    477 |     This is a normal paragraph:
    478 | 
    479 |         This is a code block.
    480 | 
    481 | Markdown will generate:
    482 | 
    483 |     

    This is a normal paragraph:

    484 | 485 |
    This is a code block.
    486 |     
    487 | 488 | One level of indentation -- 4 spaces or 1 tab -- is removed from each 489 | line of the code block. For example, this: 490 | 491 | Here is an example of AppleScript: 492 | 493 | tell application "Foo" 494 | beep 495 | end tell 496 | 497 | will turn into: 498 | 499 |

    Here is an example of AppleScript:

    500 | 501 |
    tell application "Foo"
    502 |         beep
    503 |     end tell
    504 |     
    505 | 506 | A code block continues until it reaches a line that is not indented 507 | (or the end of the article). 508 | 509 | Within a code block, ampersands (`&`) and angle brackets (`<` and `>`) 510 | are automatically converted into HTML entities. This makes it very 511 | easy to include example HTML source code using Markdown -- just paste 512 | it and indent it, and Markdown will handle the hassle of encoding the 513 | ampersands and angle brackets. For example, this: 514 | 515 | 518 | 519 | will turn into: 520 | 521 |
    <div class="footer">
    522 |         &copy; 2004 Foo Corporation
    523 |     </div>
    524 |     
    525 | 526 | Regular Markdown syntax is not processed within code blocks. E.g., 527 | asterisks are just literal asterisks within a code block. This means 528 | it's also easy to use Markdown to write about Markdown's own syntax. 529 | 530 | 531 | 532 |

    Horizontal Rules

    533 | 534 | You can produce a horizontal rule tag (`
    `) by placing three or 535 | more hyphens, asterisks, or underscores on a line by themselves. If you 536 | wish, you may use spaces between the hyphens or asterisks. Each of the 537 | following lines will produce a horizontal rule: 538 | 539 | * * * 540 | 541 | *** 542 | 543 | ***** 544 | 545 | - - - 546 | 547 | --------------------------------------- 548 | 549 | 550 | * * * 551 | 552 |

    Span Elements

    553 | 554 | 555 | 556 | Markdown supports two style of links: *inline* and *reference*. 557 | 558 | In both styles, the link text is delimited by [square brackets]. 559 | 560 | To create an inline link, use a set of regular parentheses immediately 561 | after the link text's closing square bracket. Inside the parentheses, 562 | put the URL where you want the link to point, along with an *optional* 563 | title for the link, surrounded in quotes. For example: 564 | 565 | This is [an example](http://example.com/ "Title") inline link. 566 | 567 | [This link](http://example.net/) has no title attribute. 568 | 569 | Will produce: 570 | 571 |

    This is 572 | an example inline link.

    573 | 574 |

    This link has no 575 | title attribute.

    576 | 577 | If you're referring to a local resource on the same server, you can 578 | use relative paths: 579 | 580 | See my [About](/about/) page for details. 581 | 582 | Reference-style links use a second set of square brackets, inside 583 | which you place a label of your choosing to identify the link: 584 | 585 | This is [an example][id] reference-style link. 586 | 587 | You can optionally use a space to separate the sets of brackets: 588 | 589 | This is [an example] [id] reference-style link. 590 | 591 | Then, anywhere in the document, you define your link label like this, 592 | on a line by itself: 593 | 594 | [id]: http://example.com/ "Optional Title Here" 595 | 596 | That is: 597 | 598 | * Square brackets containing the link identifier (optionally 599 | indented from the left margin using up to three spaces); 600 | * followed by a colon; 601 | * followed by one or more spaces (or tabs); 602 | * followed by the URL for the link; 603 | * optionally followed by a title attribute for the link, enclosed 604 | in double or single quotes, or enclosed in parentheses. 605 | 606 | The following three link definitions are equivalent: 607 | 608 | [foo]: http://example.com/ "Optional Title Here" 609 | [foo]: http://example.com/ 'Optional Title Here' 610 | [foo]: http://example.com/ (Optional Title Here) 611 | 612 | **Note:** There is a known bug in Markdown.pl 1.0.1 which prevents 613 | single quotes from being used to delimit link titles. 614 | 615 | The link URL may, optionally, be surrounded by angle brackets: 616 | 617 | [id]: "Optional Title Here" 618 | 619 | You can put the title attribute on the next line and use extra spaces 620 | or tabs for padding, which tends to look better with longer URLs: 621 | 622 | [id]: http://example.com/longish/path/to/resource/here 623 | "Optional Title Here" 624 | 625 | Link definitions are only used for creating links during Markdown 626 | processing, and are stripped from your document in the HTML output. 627 | 628 | Link definition names may consist of letters, numbers, spaces, and 629 | punctuation -- but they are *not* case sensitive. E.g. these two 630 | links: 631 | 632 | [link text][a] 633 | [link text][A] 634 | 635 | are equivalent. 636 | 637 | The *implicit link name* shortcut allows you to omit the name of the 638 | link, in which case the link text itself is used as the name. 639 | Just use an empty set of square brackets -- e.g., to link the word 640 | "Google" to the google.com web site, you could simply write: 641 | 642 | [Google][] 643 | 644 | And then define the link: 645 | 646 | [Google]: http://google.com/ 647 | 648 | Because link names may contain spaces, this shortcut even works for 649 | multiple words in the link text: 650 | 651 | Visit [Daring Fireball][] for more information. 652 | 653 | And then define the link: 654 | 655 | [Daring Fireball]: http://daringfireball.net/ 656 | 657 | Link definitions can be placed anywhere in your Markdown document. I 658 | tend to put them immediately after each paragraph in which they're 659 | used, but if you want, you can put them all at the end of your 660 | document, sort of like footnotes. 661 | 662 | Here's an example of reference links in action: 663 | 664 | I get 10 times more traffic from [Google] [1] than from 665 | [Yahoo] [2] or [MSN] [3]. 666 | 667 | [1]: http://google.com/ "Google" 668 | [2]: http://search.yahoo.com/ "Yahoo Search" 669 | [3]: http://search.msn.com/ "MSN Search" 670 | 671 | Using the implicit link name shortcut, you could instead write: 672 | 673 | I get 10 times more traffic from [Google][] than from 674 | [Yahoo][] or [MSN][]. 675 | 676 | [google]: http://google.com/ "Google" 677 | [yahoo]: http://search.yahoo.com/ "Yahoo Search" 678 | [msn]: http://search.msn.com/ "MSN Search" 679 | 680 | Both of the above examples will produce the following HTML output: 681 | 682 |

    I get 10 times more traffic from Google than from 684 | Yahoo 685 | or MSN.

    686 | 687 | For comparison, here is the same paragraph written using 688 | Markdown's inline link style: 689 | 690 | I get 10 times more traffic from [Google](http://google.com/ "Google") 691 | than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or 692 | [MSN](http://search.msn.com/ "MSN Search"). 693 | 694 | The point of reference-style links is not that they're easier to 695 | write. The point is that with reference-style links, your document 696 | source is vastly more readable. Compare the above examples: using 697 | reference-style links, the paragraph itself is only 81 characters 698 | long; with inline-style links, it's 176 characters; and as raw HTML, 699 | it's 234 characters. In the raw HTML, there's more markup than there 700 | is text. 701 | 702 | With Markdown's reference-style links, a source document much more 703 | closely resembles the final output, as rendered in a browser. By 704 | allowing you to move the markup-related metadata out of the paragraph, 705 | you can add links without interrupting the narrative flow of your 706 | prose. 707 | 708 | 709 |

    Emphasis

    710 | 711 | Markdown treats asterisks (`*`) and underscores (`_`) as indicators of 712 | emphasis. Text wrapped with one `*` or `_` will be wrapped with an 713 | HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML 714 | `` tag. E.g., this input: 715 | 716 | *single asterisks* 717 | 718 | _single underscores_ 719 | 720 | **double asterisks** 721 | 722 | __double underscores__ 723 | 724 | will produce: 725 | 726 | single asterisks 727 | 728 | single underscores 729 | 730 | double asterisks 731 | 732 | double underscores 733 | 734 | You can use whichever style you prefer; the lone restriction is that 735 | the same character must be used to open and close an emphasis span. 736 | 737 | Emphasis can be used in the middle of a word: 738 | 739 | un*frigging*believable 740 | 741 | But if you surround an `*` or `_` with spaces, it'll be treated as a 742 | literal asterisk or underscore. 743 | 744 | To produce a literal asterisk or underscore at a position where it 745 | would otherwise be used as an emphasis delimiter, you can backslash 746 | escape it: 747 | 748 | \*this text is surrounded by literal asterisks\* 749 | 750 | 751 | 752 |

    Code

    753 | 754 | To indicate a span of code, wrap it with backtick quotes (`` ` ``). 755 | Unlike a pre-formatted code block, a code span indicates code within a 756 | normal paragraph. For example: 757 | 758 | Use the `printf()` function. 759 | 760 | will produce: 761 | 762 |

    Use the printf() function.

    763 | 764 | To include a literal backtick character within a code span, you can use 765 | multiple backticks as the opening and closing delimiters: 766 | 767 | ``There is a literal backtick (`) here.`` 768 | 769 | which will produce this: 770 | 771 |

    There is a literal backtick (`) here.

    772 | 773 | The backtick delimiters surrounding a code span may include spaces -- 774 | one after the opening, one before the closing. This allows you to place 775 | literal backtick characters at the beginning or end of a code span: 776 | 777 | A single backtick in a code span: `` ` `` 778 | 779 | A backtick-delimited string in a code span: `` `foo` `` 780 | 781 | will produce: 782 | 783 |

    A single backtick in a code span: `

    784 | 785 |

    A backtick-delimited string in a code span: `foo`

    786 | 787 | With a code span, ampersands and angle brackets are encoded as HTML 788 | entities automatically, which makes it easy to include example HTML 789 | tags. Markdown will turn this: 790 | 791 | Please don't use any `` tags. 792 | 793 | into: 794 | 795 |

    Please don't use any <blink> tags.

    796 | 797 | You can write this: 798 | 799 | `—` is the decimal-encoded equivalent of `—`. 800 | 801 | to produce: 802 | 803 |

    &#8212; is the decimal-encoded 804 | equivalent of &mdash;.

    805 | 806 | 807 | 808 |

    Images

    809 | 810 | Admittedly, it's fairly difficult to devise a "natural" syntax for 811 | placing images into a plain text document format. 812 | 813 | Markdown uses an image syntax that is intended to resemble the syntax 814 | for links, allowing for two styles: *inline* and *reference*. 815 | 816 | Inline image syntax looks like this: 817 | 818 | ![Alt text](/path/to/img.jpg) 819 | 820 | ![Alt text](/path/to/img.jpg "Optional title") 821 | 822 | That is: 823 | 824 | * An exclamation mark: `!`; 825 | * followed by a set of square brackets, containing the `alt` 826 | attribute text for the image; 827 | * followed by a set of parentheses, containing the URL or path to 828 | the image, and an optional `title` attribute enclosed in double 829 | or single quotes. 830 | 831 | Reference-style image syntax looks like this: 832 | 833 | ![Alt text][id] 834 | 835 | Where "id" is the name of a defined image reference. Image references 836 | are defined using syntax identical to link references: 837 | 838 | [id]: url/to/image "Optional title attribute" 839 | 840 | As of this writing, Markdown has no syntax for specifying the 841 | dimensions of an image; if this is important to you, you can simply 842 | use regular HTML `` tags. 843 | 844 | 845 | * * * 846 | 847 | 848 |

    Miscellaneous

    849 | 850 | 851 | 852 | Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: 853 | 854 | 855 | 856 | Markdown will turn this into: 857 | 858 | http://example.com/ 859 | 860 | Automatic links for email addresses work similarly, except that 861 | Markdown will also perform a bit of randomized decimal and hex 862 | entity-encoding to help obscure your address from address-harvesting 863 | spambots. For example, Markdown will turn this: 864 | 865 | 866 | 867 | into something like this: 868 | 869 | address@exa 872 | mple.com 873 | 874 | which will render in a browser as a clickable link to "address@example.com". 875 | 876 | (This sort of entity-encoding trick will indeed fool many, if not 877 | most, address-harvesting bots, but it definitely won't fool all of 878 | them. It's better than nothing, but an address published in this way 879 | will probably eventually start receiving spam.) 880 | 881 | 882 | 883 |

    Backslash Escapes

    884 | 885 | Markdown allows you to use backslash escapes to generate literal 886 | characters which would otherwise have special meaning in Markdown's 887 | formatting syntax. For example, if you wanted to surround a word 888 | with literal asterisks (instead of an HTML `` tag), you can use 889 | backslashes before the asterisks, like this: 890 | 891 | \*literal asterisks\* 892 | 893 | Markdown provides backslash escapes for the following characters: 894 | 895 | \ backslash 896 | ` backtick 897 | * asterisk 898 | _ underscore 899 | {} curly braces 900 | [] square brackets 901 | () parentheses 902 | # hash mark 903 | + plus sign 904 | - minus sign (hyphen) 905 | . dot 906 | ! exclamation mark 907 | --------------------------------------------------------------------------------