diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultUser.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultUser.java new file mode 100644 index 0000000000..aa856bf336 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultUser.java @@ -0,0 +1,78 @@ +package org.mitre.openid.connect.model; + + +import com.google.common.collect.Sets; +import java.util.Collection; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +public class DefaultUser implements UserDetails { + + private String username; + private String password; + private Collection authorities; + private Boolean enabled; + + public DefaultUser() { + this.authorities = Sets.newConcurrentHashSet(); + this.enabled = true; + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + public void setAuthorities(Collection authorities) { + this.authorities = authorities; + } + + public void addAuthorities(GrantedAuthority authority) { + if (this.authorities == null) { + this.authorities = Sets.newConcurrentHashSet(); + } + this.authorities.add(authority); + } + + @Override + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username.toLowerCase(); + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java index 9ea3011d53..d83035c56a 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java @@ -42,4 +42,5 @@ public interface UserInfoRepository { */ public UserInfo getByEmailAddress(String email); + UserInfo save(UserInfo userinfo); } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserInfoService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserInfoService.java index 5b3161b1a2..53e0e0fb78 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserInfoService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserInfoService.java @@ -52,4 +52,5 @@ public interface UserInfoService { */ public UserInfo getByEmailAddress(String email); + UserInfo save(UserInfo userinfo); } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserService.java new file mode 100644 index 0000000000..1bffe7529c --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserService.java @@ -0,0 +1,15 @@ +package org.mitre.openid.connect.service; + + +import org.springframework.security.core.userdetails.UserDetails; + +public interface UserService { + + UserDetails loadUserByUsername(String username); + + void save(UserDetails user); + + void deleteUser(String username); + + void changePassword(String oldPassword, String newPassword); +} diff --git a/openid-connect-server-webapp/pom.xml b/openid-connect-server-webapp/pom.xml index e2b1f15746..ff046cbce9 100644 --- a/openid-connect-server-webapp/pom.xml +++ b/openid-connect-server-webapp/pom.xml @@ -116,6 +116,10 @@ org.hsqldb hsqldb + + mysql + mysql-connector-java + org.eclipse.persistence org.eclipse.persistence.jpa diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/loading_temp_tables.sql b/openid-connect-server-webapp/src/main/resources/db/mysql/loading_temp_tables.sql new file mode 100644 index 0000000000..1d3908ed11 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/mysql/loading_temp_tables.sql @@ -0,0 +1,75 @@ +-- +-- Temporary tables used during the bootstrapping process to safely load users and clients. +-- These are not needed if you're not using the users.sql/clients.sql files to bootstrap the database. +-- + +CREATE TEMPORARY TABLE IF NOT EXISTS authorities_TEMP ( + username varchar(50) not null, + authority varchar(50) not null, + constraint ix_authority_TEMP unique (username,authority)); + +CREATE TEMPORARY TABLE IF NOT EXISTS users_TEMP ( + username varchar(50) not null primary key, + password varchar(50) not null, + enabled boolean not null); + +CREATE TEMPORARY TABLE IF NOT EXISTS user_info_TEMP ( + sub VARCHAR(256) not null primary key, + preferred_username VARCHAR(256), + name VARCHAR(256), + given_name VARCHAR(256), + family_name VARCHAR(256), + middle_name VARCHAR(256), + nickname VARCHAR(256), + profile VARCHAR(256), + picture VARCHAR(256), + website VARCHAR(256), + email VARCHAR(256), + email_verified BOOLEAN, + gender VARCHAR(256), + zone_info VARCHAR(256), + locale VARCHAR(256), + phone_number VARCHAR(256), + address_id VARCHAR(256), + updated_time VARCHAR(256), + birthdate VARCHAR(256) +); + +CREATE TEMPORARY TABLE IF NOT EXISTS client_details_TEMP ( + client_description VARCHAR(256), + dynamically_registered BOOLEAN, + id_token_validity_seconds BIGINT, + + client_id VARCHAR(256), + client_secret VARCHAR(2048), + access_token_validity_seconds BIGINT, + refresh_token_validity_seconds BIGINT, + allow_introspection BOOLEAN, + + client_name VARCHAR(256) +); + +CREATE TEMPORARY TABLE IF NOT EXISTS client_scope_TEMP ( + owner_id VARCHAR(256), + scope VARCHAR(2048) +); + +CREATE TEMPORARY TABLE IF NOT EXISTS client_redirect_uri_TEMP ( + owner_id VARCHAR(256), + redirect_uri VARCHAR(2048) +); + +CREATE TEMPORARY TABLE IF NOT EXISTS client_grant_type_TEMP ( + owner_id VARCHAR(256), + grant_type VARCHAR(2000) +); + +CREATE TEMPORARY TABLE IF NOT EXISTS system_scope_TEMP ( + scope VARCHAR(256), + description VARCHAR(4096), + icon VARCHAR(256), + restricted BOOLEAN, + default_scope BOOLEAN, + structured BOOLEAN, + structured_param_description VARCHAR(256) +); \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml index 967f38200c..3365be3a09 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml @@ -1,6 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + - --> - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java index 4db05ebfb7..b6ecc1b782 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java @@ -16,14 +16,16 @@ *******************************************************************************/ package org.mitre.openid.connect.repository.impl; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.TypedQuery; - import org.mitre.openid.connect.model.DefaultUserInfo; import org.mitre.openid.connect.model.UserInfo; import org.mitre.openid.connect.repository.UserInfoRepository; +import org.mitre.util.jpa.JpaUtil; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; import static org.mitre.util.jpa.JpaUtil.getSingleResult; @@ -62,4 +64,10 @@ public UserInfo getByEmailAddress(String email) { return getSingleResult(query.getResultList()); } + @Override + @Transactional(value="defaultTransactionManager") + public UserInfo save(UserInfo userinfo) { + return JpaUtil.saveOrUpdate(userinfo.getPreferredUsername(), manager, userinfo); + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java index 4acc77e3b9..28b61aec6d 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java @@ -74,4 +74,9 @@ public UserInfo getByEmailAddress(String email) { return userInfoRepository.getByEmailAddress(email); } + @Override + public UserInfo save(UserInfo userinfo) { + return userInfoRepository.save(userinfo); + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserService.java new file mode 100644 index 0000000000..69ad649a91 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserService.java @@ -0,0 +1,40 @@ +package org.mitre.openid.connect.service.impl; + +import org.mitre.openid.connect.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.provisioning.UserDetailsManager; +import org.springframework.stereotype.Service; + +@Service +public class DefaultUserService implements UserService { + + @Autowired + private UserDetailsManager userDetailsManager; + + @Override + public UserDetails loadUserByUsername(String username) { + return userDetailsManager.loadUserByUsername(username); + } + + @Override + public void save(UserDetails user) { + if(userDetailsManager.userExists(user.getUsername())) { + userDetailsManager.createUser(user); + } else { + userDetailsManager.updateUser(user); + } + } + + @Override + public void deleteUser(String username) { + if(userDetailsManager.userExists(username)) { + userDetailsManager.deleteUser(username); + } + } + + @Override + public void changePassword(String oldPassword, String newPassword) { + userDetailsManager.changePassword(oldPassword, newPassword); + } +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserAPI.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserAPI.java new file mode 100644 index 0000000000..675237b8c9 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserAPI.java @@ -0,0 +1,78 @@ +package org.mitre.openid.connect.web; + +import com.google.gson.Gson; +import java.security.Principal; +import org.mitre.openid.connect.model.DefaultUser; +import org.mitre.openid.connect.model.DefaultUserInfo; +import org.mitre.openid.connect.model.UserInfo; +import org.mitre.openid.connect.service.UserInfoService; +import org.mitre.openid.connect.service.UserService; +import org.mitre.openid.connect.view.JsonEntityView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +@RequestMapping("/" + UserAPI.URL) +public class UserAPI { + public static final String URL = RootController.API_URL + "/user"; + private static final SimpleGrantedAuthority ROLE_USER = new SimpleGrantedAuthority("ROLE_USER"); + private static final Logger logger = LoggerFactory.getLogger(UserInfoAPI.class); + + @Autowired + private UserService userService; + + @Autowired + private UserInfoService userInfoService; + + private Gson gson = new Gson(); + + @RequestMapping(value = {""}, method = RequestMethod.POST) + public String save(@RequestBody String json, ModelMap m, Principal p) { + try { + DefaultUser user = gson.fromJson(json, DefaultUser.class); + if(user.getUsername() == null || user.getPassword() == null) { + m.put(JsonEntityView.ENTITY, "username is null or password is null."); + return JsonEntityView.VIEWNAME; + } + + user.setUsername(user.getUsername()); + user.setEnabled(true); + user.addAuthorities(ROLE_USER); + userService.save(user); + m.put(JsonEntityView.ENTITY, user); + + UserInfo userInfo = new DefaultUserInfo(); + userInfo.setPreferredUsername(user.getUsername()); + userInfo.setName(user.getUsername()); + userInfoService.save(userInfo); + } catch (Exception e) { + e.printStackTrace(); + m.put(JsonEntityView.ENTITY, e); + } + return JsonEntityView.VIEWNAME; + } + + @PreAuthorize("hasRole('ROLE_USER')") + @RequestMapping(value = {""}, method = RequestMethod.DELETE) + public String deleteUser(String username) { + userService.deleteUser(username); + + return JsonEntityView.VIEWNAME; + } + + @PreAuthorize("hasRole('ROLE_USER')") + @RequestMapping(value = {"/changePassword"}, method = RequestMethod.POST) + public String changePassword(String oldPassword, String newPassword) { + userService.changePassword(oldPassword, newPassword); + + return JsonEntityView.VIEWNAME; + } +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoAPI.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoAPI.java new file mode 100644 index 0000000000..5f9335d943 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoAPI.java @@ -0,0 +1,51 @@ +package org.mitre.openid.connect.web; + +import com.google.gson.Gson; +import java.security.Principal; +import org.mitre.oauth2.service.SystemScopeService; +import org.mitre.openid.connect.model.DefaultUserInfo; +import org.mitre.openid.connect.model.UserInfo; +import org.mitre.openid.connect.service.UserInfoService; +import org.mitre.openid.connect.view.JsonEntityView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +@RequestMapping("/" + UserInfoAPI.URL) +public class UserInfoAPI { + + public static final String URL = RootController.API_URL + "/userinfo"; + + @Autowired + private UserInfoService userInfoService; + + private static final Logger logger = LoggerFactory.getLogger(UserInfoAPI.class); + + private Gson gson = new Gson(); + + @PreAuthorize("hasRole('ROLE_USER') and #oauth2.hasScope('" + SystemScopeService.OPENID_SCOPE + "')") + @RequestMapping(value = {""}, method = RequestMethod.POST) + public String save(@RequestBody String json, ModelMap m, Principal p) { + try { + UserInfo _userInfo = userInfoService.getByUsername(p.getName()); + + if (_userInfo == null) { + return JsonEntityView.VIEWNAME; + } + + DefaultUserInfo userinfo = gson.fromJson(json, DefaultUserInfo.class); + UserInfo newUserinfo = userInfoService.save(userinfo); + m.put(JsonEntityView.ENTITY, newUserinfo); + } catch (Exception e) { + e.printStackTrace(); + } + return JsonEntityView.VIEWNAME; + } +}