Skip to content

Commit

Permalink
Added github oauth support (OHDSI#832)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssuvorov-fls authored and pavgra committed Feb 23, 2019
1 parent 894b05f commit ca74665
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 40 deletions.
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
<security.oauth.google.apiSecret></security.oauth.google.apiSecret>
<security.oauth.facebook.apiKey></security.oauth.facebook.apiKey>
<security.oauth.facebook.apiSecret></security.oauth.facebook.apiSecret>
<security.oauth.github.apiKey></security.oauth.github.apiKey>
<security.oauth.github.apiSecret></security.oauth.github.apiSecret>
<security.oid.clientId></security.oid.clientId>
<security.oid.apiSecret></security.oid.apiSecret>
<security.oid.url></security.oid.url>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import static org.ohdsi.webapi.shiro.management.AtlasSecurity.TOKEN_ATTRIBUTE;

import io.buji.pac4j.subject.Pac4jPrincipal;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
import java.util.Calendar;
import java.util.Collection;
Expand All @@ -13,7 +15,7 @@
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import javax.ws.rs.core.UriBuilder;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
Expand All @@ -34,14 +36,17 @@ public class UpdateAccessTokenFilter extends AdviceFilter {
private final PermissionManager authorizer;
private final int tokenExpirationIntervalInSeconds;
private final Set<String> defaultRoles;

private final String onFailRedirectUrl;

public UpdateAccessTokenFilter(
PermissionManager authorizer,
Set<String> defaultRoles,
int tokenExpirationIntervalInSeconds) {
int tokenExpirationIntervalInSeconds,
String onFailRedirectUrl) {
this.authorizer = authorizer;
this.tokenExpirationIntervalInSeconds = tokenExpirationIntervalInSeconds;
this.defaultRoles = defaultRoles;
this.onFailRedirectUrl = onFailRedirectUrl;
}

@Override
Expand All @@ -65,15 +70,26 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th
* for CAS login
*/
ShiroHttpServletRequest requestShiro = (ShiroHttpServletRequest) request;
HttpSession session = requestShiro.getSession();
if (login == null && session.getAttribute(CasHandleFilter.CONST_CAS_AUTHN) != null
&& ((String) session.getAttribute(CasHandleFilter.CONST_CAS_AUTHN)).equalsIgnoreCase("true")) {
HttpSession shiroSession = requestShiro.getSession();
if (login == null && shiroSession.getAttribute(CasHandleFilter.CONST_CAS_AUTHN) != null
&& ((String) shiroSession.getAttribute(CasHandleFilter.CONST_CAS_AUTHN)).equalsIgnoreCase("true")) {
login = ((Pac4jPrincipal) principal).getProfile().getId();
}

if (login == null) {
// user doesn't provide email - send empty token
jwt = "";
request.setAttribute(TOKEN_ATTRIBUTE, "");
// stop session to make logout of OAuth users possible
Session session = SecurityUtils.getSubject().getSession(false);
if (session != null) {
session.stop();
}

HttpServletResponse httpResponse = WebUtils.toHttp(response);

URI oauthFailURI = getOAuthFailUri();
httpResponse.sendRedirect(oauthFailURI.toString());
return false;
}
} else if (principal instanceof String) {
login = (String)principal;
Expand Down Expand Up @@ -102,6 +118,25 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th
return true;
}

private URI getOAuthFailUri() throws URISyntaxException {
return getFailUri("oauth_error_email");
}

private URI getFailUri(String failFragment) throws URISyntaxException {

URI oauthFailURI = new URI(onFailRedirectUrl);
String fragment = oauthFailURI.getFragment();
StringBuilder sbFragment = new StringBuilder();
if(fragment == null) {
sbFragment.append(failFragment).append("/");
} else if(fragment.endsWith("/")){
sbFragment.append(fragment).append(failFragment).append("/");
} else {
sbFragment.append(fragment).append("/").append(failFragment).append("/");
}
return UriBuilder.fromUri(oauthFailURI).fragment(sbFragment.toString()).build();
}

private Date getExpirationDate(final int expirationIntervalInSeconds) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, expirationIntervalInSeconds);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,72 @@
package org.ohdsi.webapi.shiro.management;

import static org.ohdsi.webapi.shiro.management.FilterTemplates.AD_FILTER;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.AUTHZ;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.CAS_AUTHC;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.CORS;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.FACEBOOK_AUTHC;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.FORCE_SESSION_CREATION;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.GITHUB_AUTHC;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.GOOGLE_AUTHC;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.HANDLE_CAS;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.HANDLE_UNSUCCESSFUL_OAUTH;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.JDBC_FILTER;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.JWT_AUTHC;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.KERBEROS_FILTER;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.LDAP_FILTER;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.LOGOUT;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.NEGOTIATE_AUTHC;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.NO_SESSION_CREATION;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.OAUTH_CALLBACK;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.OIDC_AUTH;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.SEND_TOKEN_IN_HEADER;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.SEND_TOKEN_IN_REDIRECT;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.SEND_TOKEN_IN_URL;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.SSL;
import static org.ohdsi.webapi.shiro.management.FilterTemplates.UPDATE_TOKEN;
import static org.ohdsi.webapi.util.QuoteUtils.dequote;

import io.buji.pac4j.filter.CallbackFilter;
import io.buji.pac4j.filter.SecurityFilter;
import io.buji.pac4j.realm.Pac4jRealm;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import javax.naming.Context;
import javax.servlet.Filter;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm;
import org.apache.shiro.realm.ldap.JndiLdapContextFactory;
import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.ohdsi.webapi.Constants;
import org.ohdsi.webapi.user.importer.providers.LdapProvider;
import org.ohdsi.webapi.shiro.filters.ActiveDirectoryAuthFilter;
import org.ohdsi.webapi.shiro.filters.AtlasJwtAuthFilter;
import org.ohdsi.webapi.shiro.filters.CasHandleFilter;
import org.ohdsi.webapi.shiro.filters.JdbcAuthFilter;
import org.ohdsi.webapi.shiro.filters.KerberosAuthFilter;
import org.ohdsi.webapi.shiro.filters.LdapAuthFilter;
import org.ohdsi.webapi.shiro.filters.LogoutFilter;
import org.ohdsi.webapi.shiro.filters.RedirectOnFailedOAuthFilter;
import org.ohdsi.webapi.shiro.filters.SendTokenInHeaderFilter;
import org.ohdsi.webapi.shiro.filters.SendTokenInRedirectFilter;
import org.ohdsi.webapi.shiro.filters.SendTokenInUrlFilter;
import org.ohdsi.webapi.shiro.filters.UpdateAccessTokenFilter;
import org.ohdsi.webapi.shiro.realms.ADRealm;
import org.ohdsi.webapi.shiro.filters.ActiveDirectoryAuthFilter;
import org.ohdsi.webapi.shiro.filters.JdbcAuthFilter;
import org.ohdsi.webapi.shiro.realms.JdbcAuthRealm;
import org.ohdsi.webapi.shiro.realms.JwtAuthRealm;
import org.ohdsi.webapi.shiro.filters.LdapAuthFilter;
import org.ohdsi.webapi.shiro.realms.LdapRealm;
import org.ohdsi.webapi.shiro.filters.RedirectOnFailedOAuthFilter;
import org.ohdsi.webapi.shiro.filters.SendTokenInUrlFilter;
import org.ohdsi.webapi.shiro.filters.AtlasJwtAuthFilter;
import org.ohdsi.webapi.shiro.filters.CasHandleFilter;
import org.ohdsi.webapi.shiro.filters.KerberosAuthFilter;
import org.ohdsi.webapi.user.importer.providers.LdapProvider;
import org.pac4j.cas.client.CasClient;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.oauth.client.FacebookClient;
import org.pac4j.oauth.client.GitHubClient;
import org.pac4j.oauth.client.Google2Client;
import org.pac4j.oidc.client.OidcClient;
import org.pac4j.oidc.config.OidcConfiguration;
Expand All @@ -48,19 +83,6 @@
import waffle.shiro.negotiate.NegotiateAuthenticationFilter;
import waffle.shiro.negotiate.NegotiateAuthenticationRealm;

import javax.naming.Context;
import javax.servlet.Filter;
import javax.sql.DataSource;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;

import static org.ohdsi.webapi.shiro.management.FilterTemplates.*;
import static org.ohdsi.webapi.util.QuoteUtils.dequote;

@Component
@ConditionalOnProperty(name = "security.provider", havingValue = Constants.SecurityProviders.REGULAR)
@DependsOn("flyway")
Expand Down Expand Up @@ -89,6 +111,12 @@ public class AtlasRegularSecurity extends AtlasSecurity {
@Value("${security.oauth.facebook.apiSecret}")
private String facebookApiSecret;

@Value("${security.oauth.github.apiKey}")
private String githubApiKey;

@Value("${security.oauth.github.apiSecret}")
private String githubApiSecret;

@Value("${security.ldap.dn}")
private String userDnTemplate;

Expand Down Expand Up @@ -154,7 +182,8 @@ public Map<FilterTemplates, Filter> getFilters() {
Map<FilterTemplates, Filter> filters = super.getFilters();

filters.put(LOGOUT, new LogoutFilter(eventPublisher));
filters.put(UPDATE_TOKEN, new UpdateAccessTokenFilter(this.authorizer, this.defaultRoles, this.tokenExpirationIntervalInSeconds));
filters.put(UPDATE_TOKEN, new UpdateAccessTokenFilter(this.authorizer, this.defaultRoles, this.tokenExpirationIntervalInSeconds,
this.redirectUrl));

filters.put(JWT_AUTHC, new AtlasJwtAuthFilter());
filters.put(JDBC_FILTER, new JdbcAuthFilter(eventPublisher));
Expand All @@ -176,6 +205,9 @@ public Map<FilterTemplates, Filter> getFilters() {
facebookClient.setScope("email");
facebookClient.setFields("email");

GitHubClient githubClient = new GitHubClient(this.githubApiKey, this.githubApiSecret);
githubClient.setScope("user:email");

OidcConfiguration configuration = oidcConfCreator.build();
OidcClient oidcClient = new OidcClient(configuration);

Expand All @@ -185,6 +217,7 @@ public Map<FilterTemplates, Filter> getFilters() {
this.oauthApiCallback
, googleClient
, facebookClient
, githubClient
, oidcClient
// ... put new clients here and then assign them to filters ...
)
Expand All @@ -201,6 +234,11 @@ public Map<FilterTemplates, Filter> getFilters() {
facebookOauthFilter.setClients("FacebookClient");
filters.put(FACEBOOK_AUTHC, facebookOauthFilter);

SecurityFilter githubOauthFilter = new SecurityFilter();
githubOauthFilter.setConfig(cfg);
githubOauthFilter.setClients("GitHubClient");
filters.put(GITHUB_AUTHC, githubOauthFilter);

SecurityFilter oidcFilter = new SecurityFilter();
oidcFilter.setConfig(cfg);
oidcFilter.setClients("OidcClient");
Expand Down Expand Up @@ -237,6 +275,7 @@ protected FilterChainBuilder getFilterChainBuilder() {
.addRestPath("/user/logout", LOGOUT)
.addOAuthPath("/user/oauth/google", GOOGLE_AUTHC)
.addOAuthPath("/user/oauth/facebook", FACEBOOK_AUTHC)
.addOAuthPath("/user/oauth/github", GITHUB_AUTHC)
.addPath("/user/login/cas", SSL, CORS, FORCE_SESSION_CREATION, CAS_AUTHC, UPDATE_TOKEN, SEND_TOKEN_IN_URL)
.addPath("/user/oauth/callback", SSL, HANDLE_UNSUCCESSFUL_OAUTH, OAUTH_CALLBACK)
.addPath("/user/cas/callback", SSL, HANDLE_CAS, UPDATE_TOKEN, SEND_TOKEN_IN_URL);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.ohdsi.webapi.shiro.management;

import org.ohdsi.webapi.events.EntityName;

import static javax.ws.rs.HttpMethod.GET;
import static javax.ws.rs.HttpMethod.POST;
import static org.ohdsi.webapi.events.EntityName.COHORT;
Expand All @@ -15,6 +13,8 @@
import static org.ohdsi.webapi.events.EntityName.PREDICTION;
import static org.ohdsi.webapi.events.EntityName.SOURCE;

import org.ohdsi.webapi.events.EntityName;

public enum FilterTemplates {
CREATE_COHORT_DEFINITION("createPermissionsOnCreateCohortDefinition", COHORT, POST),
CREATE_COPY_COHORT_DEFINITION("createPermissionsOnCopyCohortDefinition", COHORT, GET),
Expand Down Expand Up @@ -60,6 +60,7 @@ public enum FilterTemplates {
NEGOTIATE_AUTHC("negotiateAuthc"),
GOOGLE_AUTHC("googleAuthc"),
FACEBOOK_AUTHC("facebookAuthc"),
GITHUB_AUTHC("githubAuthc"),
CAS_AUTHC("casAuthc"),


Expand Down
12 changes: 5 additions & 7 deletions src/main/java/org/ohdsi/webapi/shiro/realms/ADRealm.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.ohdsi.webapi.shiro.realms;

import java.util.List;
import java.util.Objects;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm;
import org.apache.shiro.realm.ldap.LdapContextFactory;
Expand All @@ -14,11 +17,6 @@
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;

import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import java.util.List;
import java.util.Objects;

public class ADRealm extends ActiveDirectoryRealm {
private static final Logger LOGGER = LoggerFactory.getLogger(ADRealm.class);

Expand All @@ -45,7 +43,7 @@ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal

try {
return super.doGetAuthorizationInfo(principals);
} catch (AuthorizationException e) {
} catch (Exception e) {
LOGGER.warn(e.getMessage());
return null;
}
Expand Down
4 changes: 3 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,12 @@ security.oauth.google.apiKey=${security.oauth.google.apiKey}
security.oauth.google.apiSecret=${security.oauth.google.apiSecret}
security.oauth.facebook.apiKey=${security.oauth.facebook.apiKey}
security.oauth.facebook.apiSecret=${security.oauth.facebook.apiSecret}
security.oauth.github.apiKey=${security.oauth.github.apiKey}
security.oauth.github.apiSecret=${security.oauth.github.apiSecret}
security.oid.clientId=${security.oid.clientId}
security.oid.apiSecret=${security.oid.apiSecret}
security.oid.url=${security.oid.url}
security.oid.redirectUrl=${security.oid.url}
security.oid.redirectUrl=${security.oid.redirectUrl}
security.db.datasource.driverClassName=${security.db.datasource.driverClassName}
security.db.datasource.url=${security.db.datasource.url}
security.db.datasource.username=${security.db.datasource.username}
Expand Down

0 comments on commit ca74665

Please sign in to comment.