StatisticsController.java
package access.api;
import access.config.Config;
import access.exception.UserRestrictionException;
import access.model.User;
import access.stats.Scale;
import access.stats.Statistics;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/v1/stats")
public class StatisticsController {
private final Statistics statistics;
private final Config config;
@Autowired
public StatisticsController(Statistics statistics, Config config) {
this.statistics = statistics;
this.config = config;
}
/**
* SURFnet users (schacHomeOrganization == surfSchacHomeOrganization) see data for all IdPs (null idpEntityId).
* Non-SURFnet users are restricted to their own IdP — always enforced server-side. Anonymous callers are
* only allowed the platform-wide aggregate (see loginTimeFrame - no spEntityId filter permitted), so they
* can share the same "no restriction" idpEntityId as a super-user without being able to target one SP.
*/
private String resolveIdpEntityId(User user) {
if (user == null || user.isSuperUser()) {
return null;
}
List<String> ownerSchacHomeOrgs = config.getOwnerSchacHomeOrgs();
if (ownerSchacHomeOrgs.contains(user.getSchacHomeOrganization())) {
return null;
}
return user.getAuthenticatingAuthority();
}
/**
* Users who are restricted to their own IdP (see resolveIdpEntityId) always keep that restriction,
* regardless of any idpEntityId requested by the client. Only callers who are otherwise unrestricted
* (super users, or SURFnet users who see all IdPs) may narrow the results down to one IdP of their choosing -
* e.g. by selecting an institute in the statistics dashboard.
*/
private String resolveIdpFilter(User user, String idpEntityId) {
String restrictedIdpEntityId = resolveIdpEntityId(user);
return restrictedIdpEntityId != null ? restrictedIdpEntityId : idpEntityId;
}
//Used for retrieval of all logins for one SP
@GetMapping("/loginTimeFrame")
public List<Object> loginTimeFrame(User user,
@RequestParam("from") long from,
@RequestParam("to") long to,
@RequestParam("scale") Scale scale,
@RequestParam(value = "spEntityId", required = false) String spEntityId,
@RequestParam(value = "idpEntityId", required = false) String idpEntityId,
@RequestParam(value = "includeUnique", required = false, defaultValue = "true") boolean includeUnique) {
//Anonymous callers (this endpoint is permitAll, for the public statistics dashboard) may only ever see
//the platform-wide aggregate, never data scoped to one specific, attacker-chosen SP or IdP
if (user == null && (StringUtils.hasText(spEntityId) || StringUtils.hasText(idpEntityId))) {
throw new UserRestrictionException("Authentication is required to filter statistics by spEntityId or idpEntityId");
}
return statistics.loginTimeFrame(from, to, scale.name(), resolveIdpFilter(user, idpEntityId), spEntityId, includeUnique);
}
//Used for retrieval of all logins for all SPs
@GetMapping("/loginAggregated")
public List<Object> loginAggregated(User user,
@RequestParam("period") String period,
@RequestParam(value = "spEntityId", required = false) String spEntityId,
@RequestParam(value = "idpEntityId", required = false) String idpEntityId,
@RequestParam(value = "groupBy", required = false, defaultValue = "sp_id") String groupBy) {
return statistics.loginAggregated(period, resolveIdpFilter(user, idpEntityId), spEntityId, groupBy);
}
//Used for retrieval of all logins for one SP without a period
@GetMapping("uniqueLoginCount")
public List<Object> uniqueLoginCount(User user,
@RequestParam("from") long from,
@RequestParam("to") long to,
@RequestParam(value = "spEntityId") String spEntityId,
@RequestParam(value = "idpEntityId", required = false) String idpEntityId) {
return statistics.uniqueLoginCount(from, to, resolveIdpFilter(user, idpEntityId), spEntityId);
}
}