package com.venky.swf.controller; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TreeSet; import javax.servlet.http.HttpServletRequest; import org.apache.oltu.oauth2.client.HttpClient; import org.apache.oltu.oauth2.client.OAuthClient; import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest; import org.apache.oltu.oauth2.client.request.OAuthClientRequest; import org.apache.oltu.oauth2.client.response.GitHubTokenResponse; import org.apache.oltu.oauth2.client.response.OAuthAccessTokenResponse; import org.apache.oltu.oauth2.client.response.OAuthAuthzResponse; import org.apache.oltu.oauth2.client.response.OAuthClientResponse; import org.apache.oltu.oauth2.client.response.OAuthClientResponseFactory; import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; import org.apache.oltu.oauth2.client.response.OAuthResourceResponse; import org.apache.oltu.oauth2.common.OAuth; import org.apache.oltu.oauth2.common.OAuthProviderType; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.apache.oltu.oauth2.common.utils.OAuthUtils; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import com.venky.core.util.ObjectUtil; import com.venky.swf.controller.annotations.RequireLogin; import com.venky.swf.db.Database; import com.venky.swf.db.Transaction; import com.venky.swf.db.model.User; import com.venky.swf.db.model.UserEmail; import com.venky.swf.path.Path; import com.venky.swf.routing.Config; import com.venky.swf.sql.Expression; import com.venky.swf.sql.Operator; import com.venky.swf.sql.Select; import com.venky.swf.views.HtmlView; import com.venky.swf.views.HtmlView.StatusType; import com.venky.swf.views.RedirectorView; import com.venky.swf.views.View; public class OidController extends Controller{ public OidController(Path path) { super(path); } static class OIDProvider { public OIDProvider(String opendIdProvider, OAuthProviderType providerType, String issuer , Class< ? extends OAuthAccessTokenResponse> tokenResponseClass,String resourceUrl) { this.iss = issuer ; this.tokenResponseClass = tokenResponseClass; this.resourceUrl = resourceUrl; this.openIdProvider = opendIdProvider ; this.providerType = providerType; this.clientId = Config.instance().getClientId(opendIdProvider); this.clientSecret = Config.instance().getClientSecret(opendIdProvider) ; this.redirectUrl = Config.instance().getServerBaseUrl() + "/oid/verify?SELECTED_OPEN_ID="+opendIdProvider; } String openIdProvider; OAuthProviderType providerType; String clientId; String clientSecret; String iss; Class<? extends OAuthAccessTokenResponse> tokenResponseClass; String resourceUrl; String redirectUrl; public OAuthClientRequest createRequest(){ try { return OAuthClientRequest.authorizationProvider(providerType).setClientId(clientId).setResponseType(OAuth.OAUTH_CODE) .setScope("email"). setRedirectURI(redirectUrl).buildQueryMessage(); } catch (OAuthSystemException e) { throw new RuntimeException(e); } } public String authorize(String code){ try { OAuthClientRequest oauthRequest = OAuthClientRequest .tokenProvider(providerType) .setGrantType(GrantType.AUTHORIZATION_CODE) .setClientId(clientId) .setClientSecret(clientSecret) .setRedirectURI(redirectUrl) .setCode(code) .setScope("email") .buildBodyMessage(); OAuthClient oAuthClient = new OAuthClient(new OidHttpClient()); OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(oauthRequest, tokenResponseClass); if (ObjectUtil.isVoid(resourceUrl)){ return extractEmail(oAuthResponse); }else { String accessToken = oAuthResponse.getAccessToken(); Long expiresIn = oAuthResponse.getExpiresIn(); OAuthClientRequest bearerClientRequest = new OAuthBearerClientRequest(resourceUrl) .setAccessToken(accessToken).buildQueryMessage(); OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class); return extractEmail(resourceResponse); } } catch (Exception e) { throw new RuntimeException(e); } } public String extractEmail(OAuthResourceResponse oAuthResponse) throws Exception{ JSONObject body = (JSONObject) new JSONParser().parse(oAuthResponse.getBody()); String email = (String) body.get("email"); return email; } public String extractEmail(OAuthAccessTokenResponse oAuthResponse) throws Exception{ if (oAuthResponse instanceof OAuthJSONAccessTokenResponse){ String idToken = oAuthResponse.getParam("id_token"); StringTokenizer tk = new StringTokenizer(idToken,"."); String headerBuf = new String(Base64.getDecoder().decode(tk.nextToken())); String bodyBuf = new String(Base64.getDecoder().decode(tk.nextToken())); JSONObject header = (JSONObject) new JSONParser().parse(headerBuf); JSONObject body = (JSONObject) new JSONParser().parse(bodyBuf); String emailId = (String) body.get("email"); String[] issuers = new String[] {iss, "http://" + iss , "https://" + iss }; if (body.get("aud").equals(clientId) && Arrays.asList(issuers).contains(body.get("iss"))) { return emailId; } } throw new RuntimeException("OAuth Failed"); } } private static Map<String,OIDProvider> oidproviderMap = new HashMap<>(); static { oidproviderMap.put("GOOGLE", new OIDProvider("GOOGLE",OAuthProviderType.GOOGLE,"accounts.google.com", OAuthJSONAccessTokenResponse.class,"")); oidproviderMap.put("FACEBOOK", new OIDProvider("FACEBOOK",OAuthProviderType.FACEBOOK,"", GitHubTokenResponse.class,"https://graph.facebook.com/me?fields=email,name")); } @SuppressWarnings("rawtypes") protected View authenticate() { String selectedOpenId = getPath().getRequest().getParameter("SELECTED_OPEN_ID"); if (ObjectUtil.isVoid(selectedOpenId)){ HtmlView lv = createLoginView(); lv.setStatus(StatusType.ERROR, "Open id provider not specified"); return lv; } try { OAuthClientRequest request = oidproviderMap.get(selectedOpenId).createRequest(); RedirectorView ret = new RedirectorView(getPath()); ret.setRedirectUrl(request.getLocationUri()); return ret; } catch (Exception e) { return createLoginView(StatusType.ERROR,e.getMessage()); } } @SuppressWarnings("rawtypes") @RequireLogin(false) public View verify() throws OAuthProblemException, OAuthSystemException, ParseException{ HttpServletRequest request = getPath().getRequest(); OAuthAuthzResponse oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request); String code = oar.getCode(); String selectedOpenId = getPath().getRequest().getParameter("SELECTED_OPEN_ID"); OIDProvider provider = oidproviderMap.get(selectedOpenId); try { String email = provider.authorize(code); User u = null; Select select = new Select().from(UserEmail.class); select.where(new Expression(select.getPool(),"email",Operator.EQ, email)); List<UserEmail> oids = select.execute(UserEmail.class); int numOids = oids.size(); if (numOids > 0) { SortedSet<Integer> numUsers = new TreeSet<Integer>(); for (UserEmail oid: oids){ numUsers.add(oid.getUserId()); } if (numUsers.size() > 1) { return createLoginView(StatusType.ERROR, "Multiple users associated with same email id"); } u = Database.getTable(User.class).get(numUsers.first()); } if (u == null){ Transaction txn = Database.getInstance().getTransactionManager().createTransaction(); u = Database.getTable(User.class).newRecord(); u.setName(email); u.setPassword(null); u.save(); UserEmail oid = Database.getTable(UserEmail.class).newRecord(); oid.setUserId(u.getId()); oid.setEmail(email); oid.save(); txn.commit(); } getPath().createUserSession(u, false); return new RedirectorView(getPath(), loginSuccessful()); } catch (Exception e) { return createLoginView(StatusType.ERROR, e.getMessage()); } } public static class OidHttpClient implements HttpClient { public OidHttpClient() { } public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers, String requestMethod, Class<T> responseClass) throws OAuthSystemException, OAuthProblemException { InputStream responseBody = null; URLConnection c; Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>(); int responseCode; try { URL url = new URL(request.getLocationUri()); c = url.openConnection(); c.setConnectTimeout(5000); c.setReadTimeout(5000); responseCode = -1; if (c instanceof HttpURLConnection) { HttpURLConnection httpURLConnection = (HttpURLConnection) c; if (headers != null && !headers.isEmpty()) { for (Map.Entry<String, String> header : headers.entrySet()) { httpURLConnection.addRequestProperty(header.getKey(), header.getValue()); } } if (request.getHeaders() != null) { for (Map.Entry<String, String> header : request.getHeaders().entrySet()) { httpURLConnection.addRequestProperty(header.getKey(), header.getValue()); } } if (OAuthUtils.isEmpty(requestMethod)) { httpURLConnection.setRequestMethod(OAuth.HttpMethod.GET); } else { httpURLConnection.setRequestMethod(requestMethod); setRequestBody(request, requestMethod, httpURLConnection); } httpURLConnection.connect(); InputStream inputStream; responseCode = httpURLConnection.getResponseCode(); if (responseCode == SC_BAD_REQUEST || responseCode == SC_UNAUTHORIZED) { inputStream = httpURLConnection.getErrorStream(); } else { inputStream = httpURLConnection.getInputStream(); } responseHeaders = httpURLConnection.getHeaderFields(); responseBody = inputStream; } } catch (IOException e) { throw new OAuthSystemException(e); } return OAuthClientResponseFactory .createCustomResponse(responseBody, c.getContentType(), responseCode, responseHeaders, responseClass); } private void setRequestBody(OAuthClientRequest request, String requestMethod, HttpURLConnection httpURLConnection) throws IOException { String requestBody = request.getBody(); if (OAuthUtils.isEmpty(requestBody)) { return; } if (OAuth.HttpMethod.POST.equals(requestMethod) || OAuth.HttpMethod.PUT.equals(requestMethod)) { httpURLConnection.setDoOutput(true); OutputStream ost = httpURLConnection.getOutputStream(); PrintWriter pw = new PrintWriter(ost); pw.print(requestBody); pw.flush(); pw.close(); } } @Override public void shutdown() { // Nothing to do here } } }