Skip to content

Commit

Permalink
Merge branch 'master' into feature/SHIBUI-1058
Browse files Browse the repository at this point in the history
  • Loading branch information
Bill Smith committed Jan 28, 2019
2 parents 528417c + 7931624 commit 4b6e6cb
Show file tree
Hide file tree
Showing 42 changed files with 755 additions and 95 deletions.
4 changes: 4 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ configurations {

processResources.dependsOn(':ui:npm_run_buildProd')

jar {
enabled = true
}

//Integration of the frontend and backend into the build to have all of the UI resources available in the app's executable war
bootWar.dependsOn(':ui:npm_run_buildProd')
bootWar.baseName = 'shibui'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ class UserBootstrap {
if (shibUIConfiguration.userBootstrapResource) {
log.info("configuring users from ${shibUIConfiguration.userBootstrapResource.URI}")
new CSVReader(new InputStreamReader(shibUIConfiguration.userBootstrapResource.inputStream)).each { it ->
def (username, password, firstName, lastName, roleName) = it
def (username, password, firstName, lastName, roleName, email) = it
def role = roleRepository.findByName(roleName).orElse(new Role(name: roleName))
roleRepository.saveAndFlush(role)
def user = userRepository.findByUsername(username).orElse(new User(username: username)).with {
it.password = password
it.firstName = firstName
it.lastName = lastName
it.roles.add(role)
it.emailAddress = email
it
}
userRepository.saveAndFlush(user)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import edu.internet2.tier.shibboleth.admin.ui.security.model.User;
import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ResourceBundleMessageSource;
Expand Down Expand Up @@ -72,6 +73,6 @@ public String[] getSystemAdminEmailAddresses() {
logger.warn("No users with ROLE_ADMIN were found! Check your configuration!");
systemAdmins = new HashSet<>();
}
return systemAdmins.stream().map(User::getEmailAddress).distinct().toArray(String[]::new);
return systemAdmins.stream().filter(user -> StringUtils.isNotBlank(user.getEmailAddress())).map(User::getEmailAddress).distinct().toArray(String[]::new);
}
}
2 changes: 1 addition & 1 deletion backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ shibui.nameid-filter-ui-schema-location=classpath:nameid-filter.schema.json
# shibui.metadataProviders.taskRunRate=30000

# Email configuration (local mailhog)
spring.mail.host=localhost
spring.mail.host=mailhog
spring.mail.port=1025
spring.mail.username=username
spring.mail.password=password
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,14 @@
"refreshDelayFactor": {
"title": "label.refresh-delay-factor",
"description": "tooltip.refresh-delay-factor",
"type": "number",
"type": "string",
"widget": {
"id": "number",
"step": 0.01
"id": "string",
"help": "message.real-number"
},
"placeholder": "label.real-number",
"minimum": 0.01,
"maximum": 0.99,
"default": null
"default": "",
"pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$"
},
"minCacheDuration": {
"title": "label.min-cache-duration",
Expand Down Expand Up @@ -598,8 +597,7 @@
"certificateFile": {
"title": "label.certificate-file",
"description": "tooltip.certificate-file",
"type": "string",
"default": ""
"type": "string"
}
},
"anyOf": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,14 @@
"refreshDelayFactor": {
"title": "label.refresh-delay-factor",
"description": "tooltip.refresh-delay-factor",
"type": "number",
"type": "string",
"widget": {
"id": "number",
"step": 0.01
"id": "string",
"help": "message.real-number"
},
"placeholder": "label.real-number",
"minimum": 0,
"maximum": 0.99,
"default": null
"default": "",
"pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$"
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions backend/src/main/resources/i18n/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ message.name-must-be-unique=Name must be unique.
message.uri-valid-format=URI must be valid format.
message.id-unique=ID must be unique.
message.array-items-must-be-unique=Items in list must be unique.
message.real-number=Optional. If using a value, must be a real number between 0-1.

message.org-name-required=Organization Name is required.
message.org-displayName-required=Organization Name is required.
Expand All @@ -401,6 +402,7 @@ message.org-incomplete=These three fields must all be entered if any single fiel
message.type-required=Missing required property: Type
message.match-required=Missing required property: Match
message.value-required=Missing required property: Value
message.required=Missing required property.

message.conflict=Conflict
message.data-version-contention=Data Version Contention
Expand Down Expand Up @@ -432,6 +434,9 @@ message.required-for-regex=Required for Regex
message.file-doesnt-exist=The requested file to be processed does not exist on the server.
message.database-constraint=There was a database constraint problem processing the request. Check the request to ensure that fields that must be unique are truly unique.

message.user-request-received-title=User request received
message.user-request-received-body=Your request has been received and is being reviewed. You will be notified with access status.

tooltip.entity-id=Entity ID
tooltip.service-provider-name=Service Provider Name (Dashboard Display Only)
tooltip.force-authn=Disallows use (or reuse) of authentication results and login flows that don\u0027t provide a real-time proof of user presence in the login process
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,14 @@
"refreshDelayFactor": {
"title": "label.refresh-delay-factor",
"description": "tooltip.refresh-delay-factor",
"type": "number",
"type": "string",
"widget": {
"id": "number",
"step": 0.01
"id": "string",
"help": "message.real-number"
},
"placeholder": "label.real-number",
"minimum": 0,
"maximum": 0.99,
"default": null
"default": "",
"pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$"
},
"minCacheDuration": {
"title": "label.min-cache-duration",
Expand Down
4 changes: 2 additions & 2 deletions backend/src/test/resources/conf/1044.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"user1","password1","firstName1","lastName1","ROLE_ADMIN"
"user2","password2","firstName2","lastName2","ROLE_USER"
"user1","password1","firstName1","lastName1","ROLE_ADMIN","user1@example.org"
"user2","password2","firstName2","lastName2","ROLE_USER","user2@example.org"
13 changes: 6 additions & 7 deletions pac4j-module/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
plugins {
id 'java'
id 'groovy'
id 'com.palantir.docker' version '0.20.1'
id 'jacoco'
id 'io.franzbecker.gradle-lombok' version '1.13'
id 'org.springframework.boot' version '2.0.0.RELEASE' apply false
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
}
Expand All @@ -18,11 +17,6 @@ repositories {
}
}

lombok {
version = "1.16.20"
sha256 = "c5178b18caaa1a15e17b99ba5e4023d2de2ebc18b58cde0f5a04ca4b31c10e6d"
}

dependencyManagement {
imports {
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
Expand All @@ -39,6 +33,11 @@ dependencies {
exclude group: 'org.opensaml'
}

testCompile project(':backend')
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "org.spockframework:spock-core:1.1-groovy-2.4"
testCompile "org.spockframework:spock-spring:1.1-groovy-2.4"

annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

docker project(':backend')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package net.unicon.shibui.pac4j;

import com.fasterxml.jackson.databind.ObjectMapper;
import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse;
import edu.internet2.tier.shibboleth.admin.ui.security.model.Role;
import edu.internet2.tier.shibboleth.admin.ui.security.model.User;
import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository;
import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository;
import edu.internet2.tier.shibboleth.admin.ui.service.EmailService;
import org.apache.commons.lang3.RandomStringUtils;
import org.pac4j.saml.profile.SAML2Profile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCrypt;

import javax.mail.MessagingException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* @author Bill Smith (wsmith@unicon.net)
*/
public class AddNewUserFilter implements Filter {

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

private static final String ROLE_NONE = "ROLE_NONE";

private UserRepository userRepository;
private RoleRepository roleRepository;
private EmailService emailService;

private Pac4jConfigurationProperties pac4jConfigurationProperties;

private Pac4jConfigurationProperties.SAML2ProfileMapping saml2ProfileMapping;

public AddNewUserFilter(Pac4jConfigurationProperties pac4jConfigurationProperties, UserRepository userRepository, RoleRepository roleRepository, EmailService emailService) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.emailService = emailService;
this.pac4jConfigurationProperties = pac4jConfigurationProperties;
saml2ProfileMapping = this.pac4jConfigurationProperties.getSaml2ProfileMapping();
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

private User buildAndPersistNewUserFromProfile(SAML2Profile profile) {
Role noRole = roleRepository.findByName(ROLE_NONE).orElse(new Role(ROLE_NONE));
roleRepository.save(noRole);

User user = new User();
user.getRoles().add(noRole);
user.setUsername(getAttributeFromProfile(profile, "username"));
user.setPassword(BCrypt.hashpw(RandomStringUtils.randomAlphanumeric(20), BCrypt.gensalt()));
user.setFirstName(getAttributeFromProfile(profile, "firstName"));
user.setLastName(getAttributeFromProfile(profile, "lastName"));
user.setEmailAddress(getAttributeFromProfile(profile, "email"));
User persistedUser = userRepository.save(user);
if (logger.isDebugEnabled()) {
logger.debug("Persisted new user:\n" + user);
}
return persistedUser;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SAML2Profile profile = (SAML2Profile) authentication.getPrincipal();
if (profile != null) {
String username = getAttributeFromProfile(profile, "username");
if (username != null) {
Optional<User> persistedUser = userRepository.findByUsername(username);
User user;
if (!persistedUser.isPresent()) {
user = buildAndPersistNewUserFromProfile(profile);
try {
emailService.sendNewUserMail(username);
} catch (MessagingException e) {
logger.warn(String.format("Unable to send new user email for user [%s]", username), e);
}
} else {
user = persistedUser.get();
}
if (user.getRole().equals(ROLE_NONE)) {
((HttpServletResponse) response).sendRedirect("/unsecured/error.html");
} else {
chain.doFilter(request, response); // else, user is in the system already, carry on
}
}
}
}

@Override
public void destroy() {
}

private String getAttributeFromProfile(SAML2Profile profile, String stringKey) {
String attribute = null;
switch (stringKey) {
case "username":
attribute = saml2ProfileMapping.getUsername();
break;
case "firstName":
attribute = saml2ProfileMapping.getFirstName();
break;
case "lastName":
attribute = saml2ProfileMapping.getLastName();
break;
case "email":
attribute = saml2ProfileMapping.getEmail();
break;
default:
// do we care? Not yet.
}
List<String> attributeList = (List<String>) profile.getAttribute(attribute);
return attributeList.size() < 1 ? null : attributeList.get(0);
}

private byte[] getJsonResponseBytes(ErrorResponse eErrorResponse) throws IOException {
String errorResponseJson = new ObjectMapper().writeValueAsString(eErrorResponse);
return errorResponseJson.getBytes();
}
}
Loading

0 comments on commit 4b6e6cb

Please sign in to comment.