Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization code issue #281

Merged
merged 7 commits into from
Jul 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/docker/etc/config/oauth_client_details.csv
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ aRMT;res_ManagementPortal,res_gateway;secret;MEASUREMENT.CREATE,SUBJECT.UPDATE,S
THINC-IT;res_ManagementPortal,res_gateway;secret;MEASUREMENT.CREATE,SUBJECT.UPDATE,SUBJECT.READ,PROJECT.READ,SOURCETYPE.READ,SOURCE.READ,SOURCETYPE.READ,SOURCEDATA.READ,USER.READ,ROLE.READ;refresh_token,authorization_code;;;43200;7948800;{"dynamic_registration": true};
radar_restapi;res_ManagementPortal;secret;SUBJECT.READ,PROJECT.READ,SOURCE.READ,SOURCETYPE.READ;client_credentials;;;43200;259200;{};
radar_redcap_integrator;res_ManagementPortal;secret;PROJECT.READ,SUBJECT.CREATE,SUBJECT.READ,SUBJECT.UPDATE;client_credentials;;;43200;259200;{};
radar_dashboard;res_ManagementPortal,res_RestApi;secret;SUBJECT.READ,PROJECT.READ,SOURCE.READ,SOURCETYPE.READ;client_credentials;;;43200;259200;{};
radar_dashboard;res_ManagementPortal,res_RestApi;secret;SUBJECT.READ,PROJECT.READ,SOURCE.READ,SOURCETYPE.READ;refresh_token,authorization_code;;;43200;259200;{};
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package org.radarcns.management.config;

import static org.springframework.orm.jpa.vendor.Database.POSTGRESQL;

import io.github.jhipster.security.AjaxLogoutSuccessHandler;
import io.github.jhipster.security.Http401UnauthorizedEntryPoint;
import java.security.KeyPair;
import java.util.Arrays;
import javax.sql.DataSource;
import org.radarcns.auth.authorization.AuthoritiesConstants;
import org.radarcns.management.security.ClaimsTokenEnhancer;
import org.radarcns.management.security.PostgresApprovalStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
Expand Down Expand Up @@ -157,6 +161,9 @@ public void publishAuthenticationSuccess(Authentication authentication) {
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {

@Autowired
private JpaProperties jpaProperties;

@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
Expand All @@ -174,7 +181,12 @@ protected AuthorizationCodeServices authorizationCodeServices() {

@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
if (jpaProperties.getDatabase().equals(POSTGRESQL)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the new ApprovalStore also works for H2, perhaps rename it to SanitizedJdbcApprovalStore And remove this if/else branch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't. That's why the if else was added later on. H2 doesn't like quotes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's unfortunate... Would using hibernate solve that? Or would that be too large of a refactor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess would be not large. Currently, we do not have entities maintained by hibernate in for oauth related tables or in other words, we only have the database schema given by spring added. The rest are handled by spring-security-oauth. I assume having a local entity shouldn't effect the rest. Maybe a separate PR would be better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, a separate PR seems fine.

return new PostgresApprovalStore(dataSource);
} else {
// to have compatibility for other databases including H2
return new JdbcApprovalStore(dataSource);
}
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.radarcns.management.hibernate;

import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;

public class CaseSensitivePhysicalNamingStrategy extends SpringPhysicalNamingStrategy {

@Override
protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.radarcns.management.security;

import static org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus.APPROVED;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.util.Assert;

/**
* This class will be used to execute functions related to token approval. It is an duplicate of
* JdbcApprovalStore with escaped case sensitive fields to query.
*
* @author Dave Syer
* @modifiedBy Nivethika
*/
public class PostgresApprovalStore implements ApprovalStore {

private final JdbcTemplate jdbcTemplate;

private final Logger logger = LoggerFactory.getLogger(PostgresApprovalStore.class);

private final RowMapper<Approval> rowMapper = new AuthorizationRowMapper();

private static final String TABLE_NAME = "oauth_approvals";

private static final String FIELDS =
"\"expiresAt\", \"status\",\"lastModifiedAt\",\"userId\"," + "\"clientId\","
+ "\"scope\"";

private static final String WHERE_KEY = "where \"userId\"=? and \"clientId\"=?";

private static final String WHERE_KEY_AND_SCOPE = WHERE_KEY + " and \"scope\"=?";

private static final String AND_LESS_THAN_EXPIRE_AT = " and \"expiresAt\" <= ?";

private static final String DEFAULT_ADD_APPROVAL_STATEMENT =
String.format("insert into %s ( %s ) values (?,?,?,?,?,?)", TABLE_NAME, FIELDS);

private static final String DEFAULT_REFRESH_APPROVAL_STATEMENT = String.format(
"update %s set \"expiresAt\"=?, \"status\"=?, \"lastModifiedAt\"=? "
+ WHERE_KEY_AND_SCOPE, TABLE_NAME);

private static final String DEFAULT_GET_APPROVAL_SQL =
String.format("select %s from %s " + WHERE_KEY, FIELDS, TABLE_NAME);

private static final String DEFAULT_DELETE_APPROVAL_SQL =
String.format("delete from %s " + WHERE_KEY_AND_SCOPE + AND_LESS_THAN_EXPIRE_AT,
TABLE_NAME);

private static final String DEFAULT_EXPIRE_APPROVAL_STATEMENT =
String.format("update %s set " + "\"expiresAt\" = ? "
+ WHERE_KEY_AND_SCOPE, TABLE_NAME);

private String addApprovalStatement = DEFAULT_ADD_APPROVAL_STATEMENT;

private String refreshApprovalStatement = DEFAULT_REFRESH_APPROVAL_STATEMENT;

private String findApprovalStatement = DEFAULT_GET_APPROVAL_SQL;

private String deleteApprovalStatment = DEFAULT_DELETE_APPROVAL_SQL;

private String expireApprovalStatement = DEFAULT_EXPIRE_APPROVAL_STATEMENT;

private boolean handleRevocationsAsExpiry = false;

public PostgresApprovalStore(DataSource dataSource) {
Assert.notNull(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public void setHandleRevocationsAsExpiry(boolean handleRevocationsAsExpiry) {
this.handleRevocationsAsExpiry = handleRevocationsAsExpiry;
}

public void setAddApprovalStatement(String addApprovalStatement) {
this.addApprovalStatement = addApprovalStatement;
}

public void setFindApprovalStatement(String findApprovalStatement) {
this.findApprovalStatement = findApprovalStatement;
}

public void setDeleteApprovalStatment(String deleteApprovalStatment) {
this.deleteApprovalStatment = deleteApprovalStatment;
}

public void setExpireApprovalStatement(String expireApprovalStatement) {
this.expireApprovalStatement = expireApprovalStatement;
}

public void setRefreshApprovalStatement(String refreshApprovalStatement) {
this.refreshApprovalStatement = refreshApprovalStatement;
}

@Override
public boolean addApprovals(final Collection<Approval> approvals) {
logger.debug(String.format("adding approvals: [%s]", approvals));
boolean success = true;
for (Approval approval : approvals) {
if (!updateApproval(refreshApprovalStatement, approval) && !updateApproval(
addApprovalStatement, approval)) {
success = false;
}
}
return success;
}

@Override
public boolean revokeApprovals(Collection<Approval> approvals) {
logger.debug(String.format("Revoking approvals: [%s]", approvals));
boolean success = true;
for (final Approval approval : approvals) {
if (handleRevocationsAsExpiry) {
int refreshed = jdbcTemplate
.update(expireApprovalStatement, (ps) -> {
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
ps.setString(2, approval.getUserId());
ps.setString(3, approval.getClientId());
ps.setString(4, approval.getScope());
});
if (refreshed != 1) {
success = false;
}
} else {
int refreshed = jdbcTemplate
.update(deleteApprovalStatment, (ps) -> {
ps.setString(1, approval.getUserId());
ps.setString(2, approval.getClientId());
ps.setString(3, approval.getScope());
});
if (refreshed != 1) {
success = false;
}
}
}
return success;
}

/**
* Purges expired approvals from database.
* @return {@code true} if removed successfully, {@code false} otherwise.
*/
public boolean purgeExpiredApprovals() {
logger.debug("Purging expired approvals from database");
try {
int deleted = jdbcTemplate.update(deleteApprovalStatment, (ps) -> {
ps.setTimestamp(1, new Timestamp(new Date().getTime()));
});
logger.debug(deleted + " expired approvals deleted");
} catch (DataAccessException ex) {
logger.error("Error purging expired approvals", ex);
return false;
}
return true;
}

@Override
public List<Approval> getApprovals(String userName, String clientId) {
logger.debug("Finding approvals for userName {} and cliendId {}", userName, clientId);
return jdbcTemplate.query(findApprovalStatement, rowMapper, userName, clientId);
}

private boolean updateApproval(final String sql, final Approval approval) {
logger.debug(String.format("refreshing approval: [%s]", approval));
int refreshed = jdbcTemplate.update(sql, (ps) -> {
ps.setTimestamp(1, new Timestamp(approval.getExpiresAt().getTime()));
ps.setString(2, (approval.getStatus() == null ? APPROVED
: approval.getStatus()).toString());
ps.setTimestamp(3, new Timestamp(approval.getLastUpdatedAt().getTime()));
ps.setString(4, approval.getUserId());
ps.setString(5, approval.getClientId());
ps.setString(6, approval.getScope());
});
return refreshed == 1;
}

private static class AuthorizationRowMapper implements RowMapper<Approval> {

@Override
public Approval mapRow(ResultSet rs, int rowNum) throws SQLException {
String userName = rs.getString(4);
String clientId = rs.getString(5);
String scope = rs.getString(6);
Date expiresAt = rs.getTimestamp(1);
String status = rs.getString(2);
Date lastUpdatedAt = rs.getTimestamp(3);

return new Approval(userName, clientId, scope, expiresAt,
ApprovalStatus.valueOf(status), lastUpdatedAt);
}
}
}
2 changes: 1 addition & 1 deletion src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ spring:
hibernate:
ddl-auto: none
naming:
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
physical-strategy: org.radarcns.management.hibernate.CaseSensitivePhysicalNamingStrategy
implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
messages:
basename: i18n/messages
Expand Down