udpated security context handling; break-fix updates

This commit is contained in:
2023-02-06 21:52:53 -05:00
parent 15eed96b70
commit dfe7de8f21
11 changed files with 300 additions and 106 deletions

View File

@@ -8,6 +8,11 @@ public class Constants extends com.poststats.api.Constants {
public static final String STATEMENT_PROVIDER_GOLF = "dbstmt-golf";
public static final String STATEMENT_PROVIDER_GOLF_TX = "dbstmt-golf-tx";
public static final String BUDDY_ROLE = "buddy";
public static final String EVENT_ROLE = "event";
public static final String EVENT_SERIES_ROLE = "series";
public static final String COURSE_ROLE = "course";
public static final String BUDDY_ROLE_PREFIX = BUDDY_ROLE + ":";
public static final String EVENT_ROLE_PREFIX = "event:";
public static final String EVENT_SERIES_ROLE_PREFIX = "series:";
public static final String COURSE_ROLE_PREFIX = "course:";

View File

@@ -6,19 +6,24 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class Person extends com.poststats.security.Person {
public class AuthenticatedPerson extends com.poststats.security.AuthenticatedPerson {
private final Set<Long> buddyIds;
private final Set<String> acSids;
private final Map<Long, Set<String>> eventAcSids;
public Person(com.poststats.security.Person person, Set<String> acSids, Map<Long, Set<String>> eventAcSids) {
public AuthenticatedPerson(com.poststats.security.AuthenticatedPerson person, Set<Long> buddyIds,
Set<String> acSids, Map<Long, Set<String>> eventAcSids) {
super(person);
this.acSids = new HashSet<>(acSids);
this.acSids.addAll(person.getAccessControls());
this.buddyIds = buddyIds;
this.acSids = acSids;
this.eventAcSids = eventAcSids;
}
@Override
public Set<Long> getBuddyIds() {
return Collections.unmodifiableSet(this.buddyIds);
}
public Set<String> getAccessControls() {
return Collections.unmodifiableSet(this.acSids);
}
@@ -42,6 +47,10 @@ public class Person extends com.poststats.security.Person {
return this.acSids.contains(ac);
}
public boolean hasBuddy(long buddyId) {
return this.buddyIds.contains(buddyId);
}
public boolean hasAccessControl(String ac, long eventId) {
Set<String> sids = this.eventAcSids.get(eventId);
return sids != null && sids.contains(ac);

View File

@@ -0,0 +1,46 @@
package com.poststats.golf.security;
import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AuthenticatedSecurityContext implements SecurityContext {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final SecurityContext securityContext;
private final AuthenticatedPerson authPerson;
public AuthenticatedSecurityContext(SecurityContext securityContext, AuthenticatedPerson authPerson) {
if (securityContext == null)
throw new IllegalArgumentException();
this.securityContext = securityContext;
this.authPerson = authPerson;
}
@Override
public Principal getUserPrincipal() {
return this.authPerson;
}
@Override
public boolean isUserInRole(String role) {
this.logger.trace("isUserInRole({}, {})", this.getUserPrincipal(), role);
if (this.authPerson.hasAccessControl(role))
return true;
return this.securityContext.isUserInRole(role);
}
@Override
public String getAuthenticationScheme() {
return this.securityContext.getAuthenticationScheme();
}
@Override
public boolean isSecure() {
return this.securityContext.isSecure();
}
}

View File

@@ -1,49 +0,0 @@
package com.poststats.golf.security;
import com.poststats.golf.api.Constants;
import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EventPersonSecurityContext implements SecurityContext {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final SecurityContext securityContext;
private final long eventId;
public EventPersonSecurityContext(SecurityContext securityContext, long eventId) {
this.securityContext = securityContext;
this.eventId = eventId;
}
@Override
public Principal getUserPrincipal() {
return this.securityContext.getUserPrincipal();
}
@Override
public boolean isUserInRole(String role) {
this.logger.trace("Checking if user {} in is in role {} for event {}",
this.securityContext.getUserPrincipal().getName(), role, this.eventId);
Person person = (Person) this.securityContext.getUserPrincipal();
if (person == null) {
return false;
} else if (role.startsWith(Constants.EVENT_ROLE_PREFIX)) {
return person.hasAccessControl(role.substring(Constants.EVENT_ROLE_PREFIX.length()), this.eventId);
} else {
return person.hasAccessControl(role);
}
}
@Override
public String getAuthenticationScheme() {
return this.securityContext.getAuthenticationScheme();
}
@Override
public boolean isSecure() {
return this.securityContext.isSecure();
}
}

View File

@@ -0,0 +1,57 @@
package com.poststats.golf.security;
import com.poststats.golf.api.Constants;
import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EventSecurityContext implements SecurityContext {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final SecurityContext securityContext;
private final long eventId;
public EventSecurityContext(SecurityContext securityContext, long eventId) {
if (securityContext == null)
throw new IllegalArgumentException();
this.securityContext = securityContext;
this.eventId = eventId;
}
@Override
public Principal getUserPrincipal() {
return this.securityContext.getUserPrincipal();
}
@Override
public boolean isUserInRole(String role) {
this.logger.trace("isUserInRole({}, {}, {})", this.getUserPrincipal(), role, this.eventId);
if (role.startsWith(Constants.EVENT_ROLE_PREFIX)) {
if (this.getUserPrincipal() instanceof AuthenticatedPerson) {
AuthenticatedPerson authPerson = (AuthenticatedPerson) this.getUserPrincipal();
this.logger.trace("checking if user '{}' has role '{}' in event {}", this.getUserPrincipal(), role,
this.eventId);
if (authPerson.hasAccessControl(Constants.EVENT_ROLE, this.eventId)) {
this.logger.debug("user '{}' has role '{}' in event {}", this.getUserPrincipal(), role,
this.eventId);
return true;
}
}
}
return this.securityContext.isUserInRole(role);
}
@Override
public String getAuthenticationScheme() {
return this.securityContext.getAuthenticationScheme();
}
@Override
public boolean isSecure() {
return this.securityContext.isSecure();
}
}

View File

@@ -0,0 +1,55 @@
package com.poststats.golf.security;
import com.poststats.golf.api.Constants;
import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PersonSecurityContext implements SecurityContext {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final SecurityContext securityContext;
private final long personId;
public PersonSecurityContext(SecurityContext securityContext, long personId) {
if (securityContext == null)
throw new IllegalArgumentException();
this.securityContext = securityContext;
this.personId = personId;
}
@Override
public Principal getUserPrincipal() {
return this.securityContext.getUserPrincipal();
}
@Override
public boolean isUserInRole(String role) {
this.logger.trace("isUserInRole({}, {}, {})", this.getUserPrincipal(), role, this.personId);
if (role.equals(Constants.BUDDY_ROLE)) {
if (this.getUserPrincipal() instanceof AuthenticatedPerson) {
AuthenticatedPerson authPerson = (AuthenticatedPerson) this.getUserPrincipal();
this.logger.trace("checking if user '{}' is buddy of {}", this.getUserPrincipal(), this.personId);
if (authPerson.hasBuddy(this.personId)) {
this.logger.debug("user '{}' is buddy of {}", this.getUserPrincipal(), this.personId);
return true;
}
}
}
return this.securityContext.isUserInRole(role);
}
@Override
public String getAuthenticationScheme() {
return this.securityContext.getAuthenticationScheme();
}
@Override
public boolean isSecure() {
return this.securityContext.isSecure();
}
}

View File

@@ -1,7 +1,7 @@
package com.poststats.golf.service;
import com.brianlong.util.FlexMap;
import com.poststats.golf.security.Person;
import com.poststats.golf.security.AuthenticatedPerson;
import java.util.Collection;
import java.util.Map;
@@ -16,7 +16,7 @@ public interface PersonService {
* @param person A non-golf `UserPrincipal` object.
* @return A golfer `UserPrincipal` object.
*/
Person getUserPrincipal(com.poststats.security.Person person);
AuthenticatedPerson getUserPrincipal(com.poststats.security.AuthenticatedPerson person);
/**
* This method retrieves meta-data about the specified golfer.

View File

@@ -8,10 +8,9 @@ import com.brianlong.sql.FlexPreparedStatement;
import com.brianlong.sql.FlexStatement;
import com.brianlong.util.FlexMap;
import com.poststats.golf.api.Constants;
import com.poststats.golf.security.Person;
import com.poststats.golf.security.AuthenticatedPerson;
import com.poststats.golf.service.PersonService;
import com.poststats.golf.sql.GolfDataSource;
import com.poststats.golf.sql.GolfSQL;
import com.poststats.provider.Statement;
import com.poststats.provider.StatementProvider;
import com.poststats.service.ServiceException;
@@ -34,12 +33,12 @@ public class PersonServiceDAO extends CacheableServiceDAO<Long> implements Perso
private final int roleCacheExpirationInSeconds = 300;
private final int infoCacheExpirationInSeconds = 120;
private ClusterAwareMemoryCacher<Long, Person> principalCacher;
private ClusterAwareMemoryCacher<Long, AuthenticatedPerson> principalCacher;
@PostConstruct
private void initCache() {
this.principalCacher = new ClusterAwareMemoryCacher<Long, Person>(this.roleCacheExpirationInSeconds * 1000L,
this.clusterService.getHazelcastInstance());
this.principalCacher = new ClusterAwareMemoryCacher<Long, AuthenticatedPerson>(
this.roleCacheExpirationInSeconds * 1000L, this.clusterService.getHazelcastInstance());
}
@Override
@@ -48,11 +47,11 @@ public class PersonServiceDAO extends CacheableServiceDAO<Long> implements Perso
}
@Override
public Person getUserPrincipal(com.poststats.security.Person person) {
public AuthenticatedPerson getUserPrincipal(com.poststats.security.AuthenticatedPerson person) {
try {
return this.principalCacher.get(person.getId(), new PrincipalCacheableFetcher() {
@Override
public com.poststats.security.Person getPerson() {
public com.poststats.security.AuthenticatedPerson getPerson() {
return person;
}
});
@@ -66,28 +65,41 @@ public class PersonServiceDAO extends CacheableServiceDAO<Long> implements Perso
return this.get(Long.valueOf(personId));
}
private abstract class PrincipalCacheableFetcher implements CacheableFetcher<Long, Person> {
private abstract class PrincipalCacheableFetcher implements CacheableFetcher<Long, AuthenticatedPerson> {
public abstract com.poststats.security.Person getPerson();
public abstract com.poststats.security.AuthenticatedPerson getPerson();
@Override
public Person get(Long personId) throws SQLException {
public AuthenticatedPerson get(Long personId) throws SQLException {
Connection dbcon = GolfDataSource.getInstance().acquire(true);
try {
return new Person(this.getPerson(), queryAccessControls(dbcon, personId),
queryEventAccessControls(dbcon, personId));
return new AuthenticatedPerson(this.getPerson(), queryBuddies(personId), queryAccessControls(personId),
queryEventAccessControls(personId));
} finally {
GolfDataSource.getInstance().release(dbcon);
}
}
@Override
public Map<Long, Person> get(Collection<Long> personIds) throws SQLException, IOException {
public Map<Long, AuthenticatedPerson> get(Collection<Long> personIds) throws SQLException, IOException {
throw new UnsupportedOperationException();
}
private Set<String> queryAccessControls(Connection dbcon, long personId) throws SQLException {
FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_ACS);
private Set<Long> queryBuddies(long personId) throws SQLException {
FlexPreparedStatement fps = sqlSelectBuddyIds.buildPreparedStatement();
try {
fps.setIntegerU(1, personId);
Set<Long> set = new HashSet<>();
fps.executeQuery().getFirstColumn(Long.class, set);
return set;
} finally {
fps.close();
}
}
private Set<String> queryAccessControls(long personId) throws SQLException {
FlexPreparedStatement fps = sqlSelectAcs.buildPreparedStatement();
try {
fps.setIntegerU(1, personId);
@@ -99,8 +111,8 @@ public class PersonServiceDAO extends CacheableServiceDAO<Long> implements Perso
}
}
private Map<Long, Set<String>> queryEventAccessControls(Connection dbcon, long personId) throws SQLException {
FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_EVENT_ACS);
private Map<Long, Set<String>> queryEventAccessControls(long personId) throws SQLException {
FlexPreparedStatement fps = sqlSelectEventAcs.buildPreparedStatement();
try {
fps.setIntegerU(1, personId);
return fps.executeQuery().getFirstTwoColumnsOneToMany(Long.class, String.class);
@@ -110,6 +122,36 @@ public class PersonServiceDAO extends CacheableServiceDAO<Long> implements Perso
}
};
@Inject
@Named(Constants.STATEMENT_PROVIDER_GOLF)
@Statement(
sql = "SELECT PS1.personID "
+ "FROM ~g~.PersonSpectator PS1 "
+ " INNER JOIN ~g~.PersonSpectator PS2 ON (PS1.personID=PS2.watchedPersonID AND PS1.watchedPersonID=PS2.personID) "
+ "WHERE PS1.watchedPersonID=?"
)
private StatementProvider sqlSelectBuddyIds;
@Inject
@Named(Constants.STATEMENT_PROVIDER_GOLF)
@Statement(
sql = "SELECT AC.acSID "
+ "FROM ~g~.PersonAccessControl PAC "
+ " INNER JOIN ~p~.AccessControl AC ON (PAC.acID=AC.acID) "
+ "WHERE PAC.personID=?"
)
private StatementProvider sqlSelectAcs;
@Inject
@Named(Constants.STATEMENT_PROVIDER_GOLF)
@Statement(
sql = "SELECT EPAC.eventID, AC.acSID "
+ "FROM ~g~.EventPersonAccessControl EPAC "
+ " INNER JOIN ~p~.AccessControl AC ON (EPAC.acID=AC.acID) "
+ "WHERE EPAC.personID=?"
)
private StatementProvider sqlSelectEventAcs;
@Override
protected DataSet fetchOne(Long personId) throws SQLException {
FlexPreparedStatement fps = this.sqlSelectPerson.buildPreparedStatement();
@@ -141,18 +183,4 @@ public class PersonServiceDAO extends CacheableServiceDAO<Long> implements Perso
@Statement(sql = "SELECT GP.strokeHandicap FROM ~g~.Person P WHERE personID IN (??)")
private StatementProvider sqlSelectPersons;
private static final String SQL_SELECT_ACS = GolfSQL.changeSchema(
"SELECT AC.acSID "
+ "FROM ~g~.PersonAccessControl PAC "
+ " INNER JOIN ~p~.AccessControl AC ON (PAC.acID=AC.acID) "
+ "WHERE PAC.personID=?");
private static final String SQL_SELECT_EVENT_ACS = GolfSQL.changeSchema(
"SELECT EPAC.eventID, AC.acSID "
+ "FROM ~g~.EventPersonAccessControl EPAC "
+ " INNER JOIN ~p~.AccessControl AC ON (EPAC.acID=AC.acID) "
+ "WHERE EPAC.personID=?");
}

View File

@@ -0,0 +1,48 @@
package com.poststats.golf.servlet;
import com.poststats.golf.security.AuthenticatedSecurityContext;
import com.poststats.golf.service.PersonService;
import com.poststats.security.AuthenticatedPerson;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApplicationScoped
@Provider
@Priority(Priorities.AUTHENTICATION + 5)
public class AuthenticationFilter implements ContainerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Inject
private PersonService personService;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
SecurityContext scontext = requestContext.getSecurityContext();
if (scontext == null || scontext.getUserPrincipal() == null)
return;
AuthenticatedPerson authPerson = (AuthenticatedPerson) scontext.getUserPrincipal();
this.logger.debug("Gathering roles for golf: {}", authPerson);
com.poststats.golf.security.AuthenticatedPerson gauthPerson = this.personService.getUserPrincipal(authPerson);
if (this.logger.isTraceEnabled())
this.logger.trace("Authorized roles: {} => {}", gauthPerson.getId(), gauthPerson.getAllAccessControls());
scontext = new AuthenticatedSecurityContext(scontext, gauthPerson);
requestContext.setSecurityContext(scontext);
}
}

View File

@@ -2,8 +2,8 @@ package com.poststats.golf.servlet;
import com.brianlong.util.StringUtil;
import com.poststats.golf.api.Constants;
import com.poststats.golf.security.EventPersonSecurityContext;
import com.poststats.golf.security.Person;
import com.poststats.golf.security.AuthenticatedPerson;
import com.poststats.golf.security.EventSecurityContext;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.Priorities;
@@ -46,10 +46,10 @@ public class EventFilter implements ContainerRequestFilter {
this.logger.debug("Narrowing authorization for event: {} => {}", scontext.getUserPrincipal(), eventId);
EventPersonSecurityContext epscontext = new EventPersonSecurityContext(scontext, eventId);
EventSecurityContext epscontext = new EventSecurityContext(scontext, eventId);
if (this.logger.isTraceEnabled()) {
Person person = (Person) epscontext.getUserPrincipal();
AuthenticatedPerson person = (AuthenticatedPerson) epscontext.getUserPrincipal();
this.logger.trace("Authorized event roles: {} => {}", person.getId(),
person.getEventAccessControls(eventId));
}

View File

@@ -1,8 +1,8 @@
package com.poststats.golf.servlet;
import com.poststats.golf.api.Constants;
import com.poststats.golf.security.PersonSecurityContext;
import com.poststats.golf.service.PersonService;
import com.poststats.security.Person;
import com.poststats.security.PersonSecurityContext;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@@ -17,7 +17,7 @@ import org.slf4j.LoggerFactory;
@ApplicationScoped
@Provider
@Priority(Priorities.AUTHORIZATION - 10)
@Priority(Priorities.AUTHORIZATION - 8)
public class PersonFilter implements ContainerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@@ -27,22 +27,17 @@ public class PersonFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
SecurityContext scontext = requestContext.getSecurityContext();
if (scontext.getUserPrincipal() == null)
Long personId = (Long) requestContext.getProperty(Constants.PERSON_ID);
if (personId == null)
return;
Person person = (Person) scontext.getUserPrincipal();
SecurityContext scontext = requestContext.getSecurityContext();
if (scontext == null || scontext.getUserPrincipal() == null)
return;
this.logger.debug("Gathering roles for golf: {}", person);
com.poststats.golf.security.Person gperson = this.personService.getUserPrincipal(person);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authorized roles: {} => {}", gperson.getId(), gperson.getAllAccessControls());
}
scontext = new PersonSecurityContext(gperson);
this.logger.debug("Entering golfer context: {}", personId);
scontext = new PersonSecurityContext(scontext, personId);
requestContext.setSecurityContext(scontext);
}