Skip to content

Commit

Permalink
add rest support for attribute repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
mmoayyed committed Jul 5, 2017
1 parent 684bbc7 commit 89b6862
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 67 deletions.
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ dependencies:
- "~/.gradle/caches"
compile:
override:
- ./gradlew clean checkstyleMain bootRepackage install -x test --stacktrace
- ./gradlew clean checkstyleMain bootRepackage install -x test --stacktrace --parallel --build-cache
test:
override:
- ./gradlew checkstyleTest test --stacktrace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
import org.apereo.cas.util.CollectionUtils;
import org.apereo.services.persondir.IPersonAttributeDao;
import org.apereo.services.persondir.support.CachingPersonAttributeDaoImpl;
import org.apereo.services.persondir.support.CascadingPersonAttributeDao;
import org.apereo.services.persondir.support.GroovyPersonAttributeDao;
import org.apereo.services.persondir.support.GrouperPersonAttributeDao;
import org.apereo.services.persondir.support.JsonBackedComplexStubPersonAttributeDao;
import org.apereo.services.persondir.support.MergingPersonAttributeDaoImpl;
import org.apereo.services.persondir.support.RestfulPersonAttributeDao;
import org.apereo.services.persondir.support.jdbc.AbstractJdbcPersonAttributeDao;
import org.apereo.services.persondir.support.jdbc.MultiRowJdbcPersonAttributeDao;
import org.apereo.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao;
import org.apereo.services.persondir.support.ldap.LdaptivePersonAttributeDao;
import org.apereo.services.persondir.support.merger.IAttributeMerger;
import org.apereo.services.persondir.support.merger.MultivaluedAttributeMerger;
import org.apereo.services.persondir.support.merger.NoncollidingAttributeAdder;
import org.apereo.services.persondir.support.merger.ReplacingAttributeAdder;
Expand All @@ -33,6 +36,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.OrderComparator;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;

import javax.naming.directory.SearchControls;
import java.util.ArrayList;
Expand Down Expand Up @@ -68,6 +72,7 @@ public List<IPersonAttributeDao> attributeRepositories() {
list.addAll(jsonAttributeRepositories());
list.addAll(groovyAttributeRepositories());
list.addAll(grouperAttributeRepositories());
list.addAll(restfulAttributeRepositories());
list.addAll(stubAttributeRepositories());

OrderComparator.sort(list);
Expand All @@ -80,7 +85,7 @@ public List<IPersonAttributeDao> attributeRepositories() {
@Bean
@RefreshScope
public IPersonAttributeDao mergingAttributeRepository() {
return composeMergedAndCachedAttributeRepositories(attributeRepositories());
return cachingAttributeRepositories();
}

@ConditionalOnMissingBean(name = "jsonAttributeRepositories")
Expand Down Expand Up @@ -238,24 +243,39 @@ public List<IPersonAttributeDao> ldapAttributeRepositories() {
return list;
}

private IPersonAttributeDao composeMergedAndCachedAttributeRepositories(final List<IPersonAttributeDao> list) {
final MergingPersonAttributeDaoImpl mergingDao = new MergingPersonAttributeDaoImpl();
@ConditionalOnMissingBean(name = "restfulAttributeRepositories")
@Bean
@RefreshScope
public List<IPersonAttributeDao> restfulAttributeRepositories() {
final List<IPersonAttributeDao> list = new ArrayList<>();
casProperties.getAuthn().getAttributeRepository().getRest().forEach(rest -> {
if (StringUtils.isNotBlank(rest.getUrl())) {

final RestfulPersonAttributeDao dao = new RestfulPersonAttributeDao();
dao.setCaseInsensitiveUsername(rest.isCaseInsensitive());
dao.setOrder(rest.getOrder());
dao.setUrl(rest.getUrl());
dao.setMethod(HttpMethod.resolve(rest.getMethod()).name());

if (StringUtils.isNotBlank(rest.getBasicAuthPassword()) && StringUtils.isNotBlank(rest.getBasicAuthUsername())) {
dao.setBasicAuthPassword(rest.getBasicAuthPassword());
dao.setBasicAuthUsername(rest.getBasicAuthUsername());
LOGGER.debug("Basic authentication credentials are located for REST endpoint [{}]", rest.getUrl());
} else {
LOGGER.debug("Basic authentication credentials are not defined for REST endpoint [{}]", rest.getUrl());
}

final String merger = StringUtils.defaultIfBlank(casProperties.getAuthn().getAttributeRepository().getMerger(), "replace".trim());
LOGGER.debug("Configured merging strategy for attribute sources is [{}]", merger);
switch (merger.toLowerCase()) {
case "merge":
mergingDao.setMerger(new MultivaluedAttributeMerger());
break;
case "add":
mergingDao.setMerger(new NoncollidingAttributeAdder());
break;
case "replace":
default:
mergingDao.setMerger(new ReplacingAttributeAdder());
break;
}
LOGGER.debug("Configured REST attribute sources from [{}]", rest.getUrl());
list.add(dao);
}
});

return list;
}

@Bean
@ConditionalOnMissingBean(name = "cachingAttributeRepositories")
public IPersonAttributeDao cachingAttributeRepositories() {
final CachingPersonAttributeDaoImpl impl = new CachingPersonAttributeDaoImpl();
impl.setCacheNullResults(false);

Expand All @@ -266,17 +286,44 @@ private IPersonAttributeDao composeMergedAndCachedAttributeRepositories(final Li
.expireAfterWrite(casProperties.getAuthn().getAttributeRepository().getExpireInMinutes(), TimeUnit.MINUTES)
.build();
impl.setUserInfoCache(graphs.asMap());
impl.setCachedPersonAttributesDao(aggregatingAttributeRepositories());

LOGGER.debug("Configured cache expiration policy for merging attribute sources to be [{}] minute(s)",
casProperties.getAuthn().getAttributeRepository().getExpireInMinutes());
return impl;
}

@Bean
@ConditionalOnMissingBean(name = "aggregatingAttributeRepositories")
public IPersonAttributeDao aggregatingAttributeRepositories() {
final MergingPersonAttributeDaoImpl mergingDao = new MergingPersonAttributeDaoImpl();
final String merger = StringUtils.defaultIfBlank(casProperties.getAuthn().getAttributeRepository().getMerger(), "replace".trim());
LOGGER.debug("Configured merging strategy for attribute sources is [{}]", merger);
mergingDao.setMerger(getAttributeMerger(merger));

final List<IPersonAttributeDao> list = attributeRepositories();
mergingDao.setPersonAttributeDaos(list);
impl.setCachedPersonAttributesDao(mergingDao);

if (list.isEmpty()) {
LOGGER.debug("No attribute repository sources are available/defined to merge together.");
} else {
LOGGER.debug("Configured attribute repository sources to merge together: [{}]", list);
LOGGER.debug("Configured cache expiration policy for merging attribute sources to be [{}] minute(s)",
casProperties.getAuthn().getAttributeRepository().getExpireInMinutes());
}
return impl;

return mergingDao;
}

private IAttributeMerger getAttributeMerger(final String merger) {
switch (merger.toLowerCase()) {
case "merge":
return new MultivaluedAttributeMerger();
case "add":
return new NoncollidingAttributeAdder();
case "replace":
default:
return new ReplacingAttributeAdder();
}
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,21 @@ public class PrincipalAttributesProperties {

private Set<String> defaultAttributesToRelease = new HashSet<>();
private List<Jdbc> jdbc = new ArrayList<>();
private List<Rest> rest = new ArrayList<>();
private List<Groovy> groovy = new ArrayList();
private List<Ldap> ldap = new ArrayList();
private List<Json> json = new ArrayList();
private Stub stub = new Stub();
private Grouper grouper = new Grouper();

public List<Rest> getRest() {
return rest;
}

public void setRest(final List<Rest> rest) {
this.rest = rest;
}

public Stub getStub() {
return stub;
}
Expand Down Expand Up @@ -147,6 +156,63 @@ public void setAttributes(final Map<String, String> attributes) {
this.attributes = attributes;
}
}

public static class Rest {
private int order;
private String url;
private String method;
private boolean caseInsensitive;
private String basicAuthUsername;
private String basicAuthPassword;

public int getOrder() {
return order;
}

public void setOrder(final int order) {
this.order = order;
}

public String getUrl() {
return url;
}

public void setUrl(final String url) {
this.url = url;
}

public String getMethod() {
return method;
}

public void setMethod(final String method) {
this.method = method;
}

public boolean isCaseInsensitive() {
return caseInsensitive;
}

public void setCaseInsensitive(final boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
}

public String getBasicAuthUsername() {
return basicAuthUsername;
}

public void setBasicAuthUsername(final String basicAuthUsername) {
this.basicAuthUsername = basicAuthUsername;
}

public String getBasicAuthPassword() {
return basicAuthPassword;
}

public void setBasicAuthPassword(final String basicAuthPassword) {
this.basicAuthPassword = basicAuthPassword;
}
}

public static class Jdbc extends AbstractJpaProperties {
private static final long serialVersionUID = 6915428382578138387L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -726,8 +726,9 @@ By default, the execution order is the following but can be adjusted per source:
3. JSON
4. Groovy
5. [Internet2 Grouper](http://www.internet2.edu/products-services/trust-identity/grouper/)
6. Shibboleth
7. Stub
6. REST
7. Shibboleth
8. Stub

Note that if no *explicit* attribute mappings are defined, all permitted attributes on the record
may be retrieved by CAS from the attribute repository source and made available to the principal. On the other hand,
Expand All @@ -743,7 +744,6 @@ The following mergeing strategies can be used to resolve conflicts when the same
| `ADD` | Retains existing attribute values if any, and ignores values from subsequent sources in the resolution chain.
| `MERGE` | Combines all values into a single attribute, essentially creating a multi-valued attribute.


### Stub

Static attributes that need to be mapped to a hardcoded value belong here.
Expand Down Expand Up @@ -858,6 +858,31 @@ The format of the file may be:
}
```

### REST

If you wish to directly and separately retrieve attributes from a REST endpoint,
the following settings are then relevant:

```properties
# cas.authn.attributeRepository.rest[0].method=GET|POST
# cas.authn.attributeRepository.rest[0].order=0
# cas.authn.attributeRepository.rest[0].caseInsensitive=false
# cas.authn.attributeRepository.rest[0].basicAuthUsername=uid
# cas.authn.attributeRepository.rest[0].basicAuthPassword=password
# cas.authn.attributeRepository.rest[0].url=https://rest.somewhere.org/attributes
```

The authenticating user id is passed in form of a request parameter under `username.` The response is expected
to be a JSON map as such:

```json
{
"name" : "JohnSmith",
"age" : 29,
"messages": ["msg 1", "msg 2", "msg 3"]
}
```

### JDBC

If you wish to directly and separately retrieve attributes from a JDBC source,
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ amazonSdkVersion=1.11.155

jsonVersion=20160810
hjsonVersion=2.1.1
personDirectoryVersion=1.8.4
personDirectoryVersion=1.8.5-SNAPSHOT
quartzVersion=2.3.0

okioHttpVersion=2.7.5
Expand Down
Loading

0 comments on commit 89b6862

Please sign in to comment.