From b77ceeac56a4df8839f82052ab16b3e850cc90cb Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 22 Jul 2013 15:33:24 +0100 Subject: [PATCH] Added social from IdentityBroker - not integrated with Keycloak yet and untested! --- pom.xml | 23 +- social/pom.xml | 74 ++++++ .../org/keycloak/social/IdentityProvider.java | 50 +++++ .../social/IdentityProviderCallback.java | 113 ++++++++++ .../social/IdentityProviderState.java | 45 ++++ .../social/google/GoogleProvider.java | 117 ++++++++++ .../social/resources/SocialResource.java | 211 ++++++++++++++++++ .../social/twitter/TwitterProvider.java | 94 ++++++++ .../org/keycloak/social/util/UriBuilder.java | 85 +++++++ ...ler.services.idb.provider.IdentityProvider | 2 + 10 files changed, 813 insertions(+), 1 deletion(-) create mode 100755 social/pom.xml create mode 100644 social/src/main/java/org/keycloak/social/IdentityProvider.java create mode 100644 social/src/main/java/org/keycloak/social/IdentityProviderCallback.java create mode 100644 social/src/main/java/org/keycloak/social/IdentityProviderState.java create mode 100644 social/src/main/java/org/keycloak/social/google/GoogleProvider.java create mode 100644 social/src/main/java/org/keycloak/social/resources/SocialResource.java create mode 100644 social/src/main/java/org/keycloak/social/twitter/TwitterProvider.java create mode 100644 social/src/main/java/org/keycloak/social/util/UriBuilder.java create mode 100644 social/src/main/resources/META-INF/services/org.eventjuggler.services.idb.provider.IdentityProvider diff --git a/pom.xml b/pom.xml index d01a72c6a3..55f9336096 100755 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,8 @@ core services integration - examples + examples + social @@ -176,6 +177,26 @@ hibernate-jpa-2.0-api 1.0.1.Final + + com.google.api-client + google-api-client + 1.14.1-beta + + + com.google.http-client + google-http-client-jackson + 1.14.1-beta + + + com.google.apis + google-api-services-oauth2 + v2-rev35-1.14.1-beta + + + org.twitter4j + twitter4j-core + 3.0.3 + diff --git a/social/pom.xml b/social/pom.xml new file mode 100755 index 0000000000..bb982ebf33 --- /dev/null +++ b/social/pom.xml @@ -0,0 +1,74 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-1 + ../pom.xml + + 4.0.0 + + keycloak-social + Keycloak Social + + + + + org.jboss.resteasy + resteasy-jaxrs + provided + + + org.jboss.resteasy + jaxrs-api + provided + + + org.picketlink + picketlink-idm-api + + + + org.jboss.spec.javax.ejb + jboss-ejb-api_3.2_spec + provided + 1.0.0.Alpha2 + + + + com.google.api-client + google-api-client + provided + + + com.google.http-client + google-http-client-jackson + provided + + + com.google.apis + google-api-services-oauth2 + provided + + + + org.twitter4j + twitter4j-core + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + + diff --git a/social/src/main/java/org/keycloak/social/IdentityProvider.java b/social/src/main/java/org/keycloak/social/IdentityProvider.java new file mode 100644 index 0000000000..202e82da78 --- /dev/null +++ b/social/src/main/java/org/keycloak/social/IdentityProvider.java @@ -0,0 +1,50 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.social; + +import java.net.URI; + +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; + +import org.picketlink.idm.model.User; + +/** + * @author Stian Thorgersen + */ +@XmlRootElement +public interface IdentityProvider { + + String getId(); + + @XmlTransient + URI getAuthUrl(IdentityProviderCallback callback); + + String getName(); + + @XmlTransient + boolean isCallbackHandler(IdentityProviderCallback callback); + + @XmlTransient + User processCallback(IdentityProviderCallback callback); + +} diff --git a/social/src/main/java/org/keycloak/social/IdentityProviderCallback.java b/social/src/main/java/org/keycloak/social/IdentityProviderCallback.java new file mode 100644 index 0000000000..0ecbd9fb00 --- /dev/null +++ b/social/src/main/java/org/keycloak/social/IdentityProviderCallback.java @@ -0,0 +1,113 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.social; + +import java.net.URI; +import java.util.List; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.UriInfo; + +import org.keycloak.social.util.UriBuilder; + +/** + * @author Stian Thorgersen + */ +public class IdentityProviderCallback { + + private String application; + + private HttpHeaders headers; + + private String providerKey; + + private String providerSecret; + + private IdentityProviderState providerState; + + private UriInfo uriInfo; + + public boolean containsQueryParam(String key) { + return uriInfo.getQueryParameters().containsKey(key); + } + + public boolean containsState(String key) { + return providerState.contains(key); + } + + public UriBuilder createUri(String path) { + return new UriBuilder(headers, uriInfo, path); + } + + public URI getProviderCallbackUrl() { + return createUri("social/" + application + "/callback").build(); + } + + public String getProviderKey() { + return providerKey; + } + + public String getProviderSecret() { + return providerSecret; + } + + public String getQueryParam(String key) { + List values = uriInfo.getQueryParameters().get(key); + if (!values.isEmpty()) { + return values.get(0); + } + return null; + } + + public T getState(String key) { + return providerState.remove(key); + } + + public void putState(String key, Object value) { + providerState.put(key, value); + } + + public void setApplication(String application) { + this.application = application; + } + + public void setHeaders(HttpHeaders headers) { + this.headers = headers; + } + + public void setProviderKey(String providerKey) { + this.providerKey = providerKey; + } + + public void setProviderSecret(String providerSecret) { + this.providerSecret = providerSecret; + } + + public void setProviderState(IdentityProviderState providerState) { + this.providerState = providerState; + } + + public void setUriInfo(UriInfo uriInfo) { + this.uriInfo = uriInfo; + } + +} diff --git a/social/src/main/java/org/keycloak/social/IdentityProviderState.java b/social/src/main/java/org/keycloak/social/IdentityProviderState.java new file mode 100644 index 0000000000..e8f9aebaf4 --- /dev/null +++ b/social/src/main/java/org/keycloak/social/IdentityProviderState.java @@ -0,0 +1,45 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.social; + + +/** + * @author Stian Thorgersen + */ +public class IdentityProviderState { + + private final Map state = Collections.synchronizedMap(new HashMap()); + + public boolean contains(String key) { + return state.containsKey(key); + } + + public void put(String key, Object value) { + state.put(key, value); + } + + @SuppressWarnings("unchecked") + public T remove(String key) { + return (T) state.remove(key); + } + +} diff --git a/social/src/main/java/org/keycloak/social/google/GoogleProvider.java b/social/src/main/java/org/keycloak/social/google/GoogleProvider.java new file mode 100644 index 0000000000..cb180c9bc7 --- /dev/null +++ b/social/src/main/java/org/keycloak/social/google/GoogleProvider.java @@ -0,0 +1,117 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.social.google; + +import java.net.URI; +import java.util.UUID; + +import org.jboss.resteasy.logging.Logger; +import org.keycloak.social.IdentityProvider; +import org.keycloak.social.IdentityProviderCallback; +import org.picketlink.idm.model.SimpleUser; +import org.picketlink.idm.model.User; + +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest; +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson.JacksonFactory; +import com.google.api.services.oauth2.Oauth2; +import com.google.api.services.oauth2.model.Tokeninfo; +import com.google.api.services.oauth2.model.Userinfo; + +/** + * @author Stian Thorgersen + */ +public class GoogleProvider implements IdentityProvider { + + private static final JacksonFactory JSON_FACTORY = new JacksonFactory(); + + private static final Logger log = Logger.getLogger(GoogleProvider.class); + + private static final NetHttpTransport TRANSPORT = new NetHttpTransport(); + + @Override + public String getId() { + return "google"; + } + + @Override + public URI getAuthUrl(IdentityProviderCallback callback) { + String state = UUID.randomUUID().toString(); + callback.putState(state, null); + + return callback + .createUri("https://accounts.google.com/o/oauth2/auth") + .setQueryParam("client_id", callback.getProviderKey()) + .setQueryParam("response_type", "code") + .setQueryParam("scope", + "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email") + .setQueryParam("redirect_uri", callback.getProviderCallbackUrl().toString()).setQueryParam("state", state) + .build(); + } + + @Override + public String getName() { + return "Google"; + } + + @Override + public boolean isCallbackHandler(IdentityProviderCallback callback) { + return callback.containsQueryParam("state") && callback.containsState(callback.getQueryParam("state")); + } + + @Override + public User processCallback(IdentityProviderCallback callback) { + String code = callback.getQueryParam("code"); + + try { + GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(TRANSPORT, JSON_FACTORY, + callback.getProviderKey(), callback.getProviderSecret(), code, callback.getProviderCallbackUrl().toString()) + .execute(); + + GoogleCredential credential = new GoogleCredential.Builder().setJsonFactory(JSON_FACTORY).setTransport(TRANSPORT) + .setClientSecrets(callback.getProviderKey(), callback.getProviderSecret()).build() + .setFromTokenResponse(tokenResponse); + + Oauth2 oauth2 = new Oauth2.Builder(TRANSPORT, JSON_FACTORY, credential).build(); + + Tokeninfo tokenInfo = oauth2.tokeninfo().setAccessToken(credential.getAccessToken()).execute(); + + if (tokenInfo.containsKey("error")) { + throw new RuntimeException("error"); + } + + Userinfo userInfo = oauth2.userinfo().get().execute(); + User user = new SimpleUser(userInfo.getEmail()); + user.setFirstName(userInfo.getGivenName()); + user.setLastName(userInfo.getFamilyName()); + user.setEmail(userInfo.getEmail()); + + return user; + } catch (Exception e) { + log.error("Failed to process callback", e); + return null; + } + } + +} diff --git a/social/src/main/java/org/keycloak/social/resources/SocialResource.java b/social/src/main/java/org/keycloak/social/resources/SocialResource.java new file mode 100644 index 0000000000..ec8c5dec45 --- /dev/null +++ b/social/src/main/java/org/keycloak/social/resources/SocialResource.java @@ -0,0 +1,211 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.social.resources; + +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.imageio.spi.ServiceRegistry; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.keycloak.social.IdentityProvider; +import org.keycloak.social.IdentityProviderCallback; +import org.keycloak.social.IdentityProviderState; +import org.keycloak.social.util.UriBuilder; +import org.picketlink.idm.model.Attribute; +import org.picketlink.idm.model.User; + +/** + * @author Stian Thorgersen + */ +@Path("social") +public class SocialResource { + + // TODO This is just temporary - need to either save state variables somewhere they can be flushed after a timeout, or + // alternatively they could be saved in http session, but that is probably not a good idea + private static final Map states = new HashMap(); + + private static synchronized IdentityProviderState getProviderState(IdentityProvider provider) { + IdentityProviderState s = states.get(provider.getId()); + if (s == null) { + s = new IdentityProviderState(); + states.put(provider.getId(), s); + } + return s; + } + + @Context + private HttpHeaders headers; + + @Context + private UriInfo uriInfo; + + @GET + @Path("{application}/callback") + public Response callback(@PathParam("application") String application) throws URISyntaxException { + String realm = null; // TODO Get realm for application + + IdentityProviderCallback callback = new IdentityProviderCallback(); + callback.setApplication(application); + callback.setHeaders(headers); + callback.setUriInfo(uriInfo); + + Iterator itr = ServiceRegistry.lookupProviders(IdentityProvider.class); + + for (IdentityProvider provider = itr.next(); itr.hasNext();) { + callback.setProviderState(getProviderState(provider)); + + if (provider.isCallbackHandler(callback)) { + User user = provider.processCallback(callback); + if (user == null) { + break; + } + + String providerUsername = user.getLoginName(); + String providerUsernameKey = provider.getId() + ".username"; + + user.setAttribute(new Attribute(providerUsernameKey, user.getLoginName())); + + User existingUser = getUser(realm, user.getLoginName()); + + if (existingUser != null) { + user = mergeUser(user, existingUser); + + updateUser(realm, user); + } else { + if (user.getEmail() != null && getUser(realm, user.getEmail()) == null) { + user.setLoginName(user.getEmail()); + } else if (getUser(realm, user.getLoginName()) != null) { + for (int i = 0;; i++) { + if (getUser(realm, providerUsername + i) == null) { + user.setLoginName(providerUsername + i); + break; + } + } + } + + createUser(realm, user); + } + + // TODO Get bearer token etc and redirect to application callback url + URI uri = null; + return Response.seeOther(uri).build(); + } + } + + return redirectToLogin(application, "login_failed"); + } + + private void createUser(String realm, User user) { + // TODO Save user in IDM + } + + @GET + @Path("providers") + public List getProviders() { + List providers = new LinkedList(); + Iterator itr = ServiceRegistry.lookupProviders(IdentityProvider.class); + while (itr.hasNext()) { + providers.add(itr.next()); + } + return providers; + } + + private User getUser(String realm, String username) { + // TODO Get user from IDM + return null; + } + + private User mergeUser(User source, User destination) { + if (source.getEmail() != null) { + destination.setEmail(source.getEmail()); + } + + if (source.getFirstName() != null) { + destination.setFirstName(source.getFirstName()); + } + + if (source.getLastName() != null) { + destination.setLastName(source.getLastName()); + } + + for (Attribute attribute : source.getAttributes()) { + destination.setAttribute(attribute); + } + + return destination; + } + + private Response redirectToLogin(String application, String error) { + URI uri = new UriBuilder(headers, uriInfo, "login?application=" + application + "&error=login_failed").build(); + return Response.seeOther(uri).build(); + } + + @GET + @Path("{application}/auth/{provider}") + @Produces(MediaType.TEXT_HTML) + public Response redirectToProviderAuth(@PathParam("application") String application, + @PathParam("provider") String providerId) { + Iterator itr = ServiceRegistry.lookupProviders(IdentityProvider.class); + + IdentityProvider provider; + for (provider = itr.next(); itr.hasNext() && !provider.getId().equals(providerId);) { + } + + if (provider == null) { + return redirectToLogin(application, "invalid_provider"); + } + + IdentityProviderCallback callback = new IdentityProviderCallback(); + callback.setApplication(application); + callback.setHeaders(headers); + callback.setUriInfo(uriInfo); + callback.setProviderKey(null); // TODO Get provider key + callback.setProviderSecret(null); // TODO Get provider secret + + URI authUrl = provider.getAuthUrl(callback); + if (authUrl != null) { + return Response.seeOther(authUrl).build(); + } else { + return redirectToLogin(application, "invalid_provider"); + } + } + + private void updateUser(String realm, User user) { + // TODO Update user in IDM + } + +} diff --git a/social/src/main/java/org/keycloak/social/twitter/TwitterProvider.java b/social/src/main/java/org/keycloak/social/twitter/TwitterProvider.java new file mode 100644 index 0000000000..3a28e14b4f --- /dev/null +++ b/social/src/main/java/org/keycloak/social/twitter/TwitterProvider.java @@ -0,0 +1,94 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.social.twitter; + +import java.net.URI; + +import org.jboss.resteasy.logging.Logger; +import org.keycloak.social.IdentityProvider; +import org.keycloak.social.IdentityProviderCallback; +import org.picketlink.idm.model.SimpleUser; +import org.picketlink.idm.model.User; + +import twitter4j.Twitter; +import twitter4j.TwitterFactory; +import twitter4j.auth.RequestToken; + +/** + * @author Stian Thorgersen + */ +public class TwitterProvider implements IdentityProvider { + + private static final Logger log = Logger.getLogger(TwitterProvider.class); + + @Override + public String getId() { + return "twitter"; + } + + @Override + public URI getAuthUrl(IdentityProviderCallback callback) { + try { + Twitter twitter = new TwitterFactory().getInstance(); + twitter.setOAuthConsumer(callback.getProviderKey(), callback.getProviderSecret()); + + RequestToken requestToken = twitter.getOAuthRequestToken(); + callback.putState(requestToken.getToken(), requestToken); + return callback.createUri(requestToken.getAuthenticationURL()).build(); + } catch (Exception e) { + log.error("Failed to retrieve login url", e); + return null; + } + } + + @Override + public String getName() { + return "Twitter"; + } + + @Override + public boolean isCallbackHandler(IdentityProviderCallback callback) { + return callback.containsQueryParam("oauth_token") && callback.containsState(callback.getQueryParam("oauth_token")); + } + + @Override + public User processCallback(IdentityProviderCallback callback) { + try { + Twitter twitter = new TwitterFactory().getInstance(); + twitter.setOAuthConsumer(callback.getProviderKey(), callback.getProviderSecret()); + + String verifier = callback.getQueryParam("oauth_verifier"); + RequestToken requestToken = callback.getState(callback.getQueryParam("oauth_token")); + + twitter.getOAuthAccessToken(requestToken, verifier); + twitter4j.User twitterUser = twitter.verifyCredentials(); + + User user = new SimpleUser(String.valueOf(twitterUser.getScreenName())); + user.setFirstName(twitterUser.getName()); + return user; + } catch (Exception e) { + log.error("Failed to process callback", e); + return null; + } + } + +} diff --git a/social/src/main/java/org/keycloak/social/util/UriBuilder.java b/social/src/main/java/org/keycloak/social/util/UriBuilder.java new file mode 100644 index 0000000000..5db382f870 --- /dev/null +++ b/social/src/main/java/org/keycloak/social/util/UriBuilder.java @@ -0,0 +1,85 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.social.util; + +import java.net.URI; +import java.net.URISyntaxException; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.UriInfo; + +/** + * @author Stian Thorgersen + */ +public class UriBuilder { + + private final javax.ws.rs.core.UriBuilder b; + + private String fragment; + + public UriBuilder(HttpHeaders headers, UriInfo uriInfo, String path) { + if (path.contains("#")) { + String t = path; + path = t.substring(0, t.indexOf('#')); + fragment = t.substring(t.indexOf('#')); + } + + if (path.contains("://")) { + b = javax.ws.rs.core.UriBuilder.fromUri(path); + } else { + URI absolutePath = uriInfo.getAbsolutePath(); + + if (headers.getRequestHeaders().containsKey("x-forwarded-proto")) { + String scheme = headers.getRequestHeaders().get("x-forwarded-proto").get(0); + try { + absolutePath = new URI(absolutePath.toString().replaceFirst(".*://", scheme + "://")); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + if (path.startsWith("/")) { + b = javax.ws.rs.core.UriBuilder.fromUri(absolutePath.resolve(path)); + } else { + URI uri = absolutePath; + String p = uri.getPath(); + p = p.substring(0, p.indexOf('/', 1) + 1); + uri = uri.resolve(p + path); + b = javax.ws.rs.core.UriBuilder.fromUri(uri); + } + } + } + + public URI build() { + URI uri = b.build(); + if (fragment != null) { + uri = uri.resolve(fragment); + } + return uri; + } + + public UriBuilder setQueryParam(String name, String value) { + b.replaceQueryParam(name, value); + return this; + } + +} diff --git a/social/src/main/resources/META-INF/services/org.eventjuggler.services.idb.provider.IdentityProvider b/social/src/main/resources/META-INF/services/org.eventjuggler.services.idb.provider.IdentityProvider new file mode 100644 index 0000000000..401ae50264 --- /dev/null +++ b/social/src/main/resources/META-INF/services/org.eventjuggler.services.idb.provider.IdentityProvider @@ -0,0 +1,2 @@ +org.keycloak.social.google.GoogleProvider +org.keycloak.social.twitter.TwitterProvider \ No newline at end of file