diff --git a/README.md b/README.md index aab4c1f..c2182b5 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,13 @@ You must have a recipe database file (see below) to store the recipes in. By default, it will be looked for in your home directory. +As of the 0.1.4 release, the parts of the application that can +alter the database are now password-protected. You will need +a ``.gourmetpw`` file to contain your userid/password +definitions. By default it should be in the same directory +that you are running the application from. A sample password file +is included in this project. + To actually access the application, open your web browser to ``http://localhost:8080`` diff --git a/application.properties b/application.properties index 5b4f5d8..d1f779d 100644 --- a/application.properties +++ b/application.properties @@ -17,3 +17,6 @@ spring.datasource.driverClassName=org.sqlite.JDBC #spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect spring.jpa.database-platform=org.sqlite.hibernate.dialect.SQLiteDialect #spring.jpa.show-sql: true + +# My special properties +gourmet.password.file=.gourmetpw diff --git a/gourmetpw.sample b/gourmetpw.sample new file mode 100644 index 0000000..2d4807c --- /dev/null +++ b/gourmetpw.sample @@ -0,0 +1,15 @@ +# This is a sample password file for the Gourmetj webapp. +# The actual file should be named ".gourmetpw" and located +# in the same directory that you run the application from. +# +# Blank lines and lines beginning with "#" are ignored. +# Password lines look like this (remove leading "#") +# +# userid,password,role[,role,...] +# +# like so: +# +# john.smith,secretpassword,USER +# +# Where "role" is a security role. ADMIN is also allowed. +# diff --git a/src/main/java/com/mousetech/gourmetj/SpringSecurityConfig.java b/src/main/java/com/mousetech/gourmetj/SpringSecurityConfig.java index 683f4f9..6064ff3 100644 --- a/src/main/java/com/mousetech/gourmetj/SpringSecurityConfig.java +++ b/src/main/java/com/mousetech/gourmetj/SpringSecurityConfig.java @@ -1,8 +1,19 @@ package com.mousetech.gourmetj; +import java.io.File; +import java.io.FileReader; +import java.io.LineNumberInputStream; +import java.io.LineNumberReader; +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer; +import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -12,6 +23,11 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { + /* Logger */ + + private static final Logger log = + LoggerFactory.getLogger(SpringSecurityConfig.class); + @Override protected void configure(HttpSecurity http) throws Exception { @@ -32,12 +48,55 @@ public class SpringSecurityConfig http.csrf().disable(); } + @Value("${gourmet.password.file}") + private String passwordFile; + @Autowired public void configureGlobal( AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication().withUser("tim.holloway") - .password("{noop}secret").roles("ADMIN").and() - .withUser("jane.doe").password("{noop}5678") - .roles("USER"); + + File pwFile = new File(passwordFile); + if (!pwFile.canRead()) { + String msg = + "Password file '" + pwFile.getAbsolutePath() + + "' could not be found or read."; + log.error(msg); + throw new RuntimeException(msg); + } + + LineNumberReader rdr = + new LineNumberReader(new FileReader(pwFile)); + String pwLine; + InMemoryUserDetailsManagerConfigurer authenticator = + auth.inMemoryAuthentication(); + while ((pwLine = rdr.readLine()) != null) { + pwLine = pwLine.trim(); + if (( pwLine.length() == 0) || (pwLine.charAt(0) == '#')) { + continue; + } + String[] creds = parseCreds(pwLine); + UserDetailsManagerConfigurer>.UserDetailsBuilder bar = + authenticator.withUser(creds[0]) + .password("{noop}"+creds[1]); + int credlen = creds.length; + for (int i = 2; i < credlen; i++) { + bar.roles(creds[i]); + } + } + rdr.close(); + } + + /** + * Parse CSV credential/roles line. Element 1 is userid, + * element 2 is password, following element(s) are role(s) + * + * @param pwLine + * @return Credentials array following CSV values, trimmed + */ + private String[] parseCreds(String pwLine) { + String[] creds = pwLine.split(","); + String[] ocreds = Arrays.stream(creds).map(e -> e.trim()) + .toArray(String[]::new); + return ocreds; } }