diff --git a/homework-g596-stepanova/pom.xml b/homework-g596-stepanova/pom.xml index d60c06fd1..78c4ce843 100644 --- a/homework-g596-stepanova/pom.xml +++ b/homework-g596-stepanova/pom.xml @@ -9,6 +9,10 @@ 4.0.0 + + 1.4.2.RELEASE + + homework-g596-stepanova @@ -23,6 +27,36 @@ 1.0.0 test + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-jdbc + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-security + ${spring.boot.version} + + + + com.zaxxer + HikariCP + 2.5.1 + + + + com.h2database + h2 + 1.4.193 + diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingDatabaseConfiguration.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingDatabaseConfiguration.java new file mode 100644 index 000000000..1bcd7243f --- /dev/null +++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingDatabaseConfiguration.java @@ -0,0 +1,26 @@ +package ru.mipt.java2016.homework.g596.stepanova.task4; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +@Configuration +public class BillingDatabaseConfiguration { + @Bean + public DataSource billingDataSource( + @Value("${ru.mipt.java2016.homework.g000.lavrentyev.task4.jdbcUrl}") String jdbcUrl, + @Value("${ru.mipt.java2016.homework.g000.lavrentyev.task4.username:}") String username, + @Value("${ru.mipt.java2016.homework.g000.lavrentyev.task4.password:}") + String password) { + HikariConfig config = new HikariConfig(); + config.setDriverClassName(org.h2.Driver.class.getName()); + config.setJdbcUrl(jdbcUrl); + config.setUsername(username); + config.setPassword(password); + return new HikariDataSource(config); + } +} \ No newline at end of file diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingUser.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingUser.java new file mode 100644 index 000000000..2b5c2db10 --- /dev/null +++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/BillingUser.java @@ -0,0 +1,66 @@ +package ru.mipt.java2016.homework.g596.stepanova.task4; + +public class BillingUser { + private final String username; + private final String password; + private final boolean enabled; + + public BillingUser(String username, String password, boolean enabled) { + if (username == null) { + throw new IllegalArgumentException("Null username is not allowed"); + } + if (password == null) { + throw new IllegalArgumentException("Null password is not allowed"); + } + this.username = username; + this.password = password; + this.enabled = enabled; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public boolean isEnabled() { + return enabled; + } + + @Override + public String toString() { + return "BillingUser{" + "username='" + username + '\'' + ", password='" + password + '\'' + + ", enabled=" + enabled + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BillingUser that = (BillingUser) o; + + if (enabled != that.enabled) { + return false; + } + if (!username.equals(that.username)) { + return false; + } + return password.equals(that.password); + + } + + @Override + public int hashCode() { + int result = username.hashCode(); + result = 31 * result + password.hashCode(); + result = 31 * result + (enabled ? 1 : 0); + return result; + } +} \ No newline at end of file diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/CalculatorController.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/CalculatorController.java new file mode 100644 index 000000000..4b37d4b8b --- /dev/null +++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/CalculatorController.java @@ -0,0 +1,129 @@ +package ru.mipt.java2016.homework.g596.stepanova.task4; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import ru.mipt.java2016.homework.base.task1.Calculator; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; + +@RestController +public class CalculatorController { + private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class); + @Autowired private Calculator calculator; + @Autowired private Database database; + @Autowired private Database clients; + + private static final Set SYMBOLS = new TreeSet<>( + Arrays.asList('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z')); + + @RequestMapping(path = "/ping", method = RequestMethod.GET, produces = "text/plain") + public String echo() { + return "OK\n"; + } + + @RequestMapping(path = "/", method = RequestMethod.GET, produces = "text/html") + public String main(@RequestParam(required = false) String name) { + if (name == null) { + name = "world"; + } + return "" + "MyApp" + "

Hello, " + name + + "!

" + ""; + } + + @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain") + public String eval(Authentication authentication, @RequestBody String expression) + throws ParsingException { + String author = authentication.getName(); + if (clients.checkUser(author)) { + LOG.debug("Evaluation request: [" + expression + "]"); + StringBuilder expressionNew = new StringBuilder(); + StringBuilder word = new StringBuilder(); + boolean letter = false; + for (int i = 0; i < expression.length(); ++i) { + if (SYMBOLS.contains(expression.charAt(i))) { + letter = true; + word.append(expression.charAt(i)); + } else { + if (letter) { + letter = false; + try { + expressionNew.append(database.loadMeaning(word.toString())); + } catch (Exception e) { + throw new ParsingException(e.getMessage(), e.getCause()); + } + expressionNew.append(expression.charAt(i)); + word = new StringBuilder(); + } else { + expressionNew.append(expression.charAt(i)); + } + } + } + System.out.println(expressionNew.toString()); + double result = calculator.calculate(expressionNew.toString()); + LOG.trace("Result: " + result); + return Double.toString(result) + "\n"; + } else { + return "Has no permission!\n"; + } + } + + @RequestMapping(path = "/add", method = RequestMethod.POST, + consumes = "text/plain", produces = "text/plain") + public String add(@RequestBody String argument) throws ParsingException { + LOG.debug("Add request: [" + argument + "]"); + String delim = "[;]"; + String[] tokens = argument.split(delim); + database.register(tokens); + return "OK\n"; + } + + @RequestMapping(path = "/delete", method = RequestMethod.DELETE, + consumes = "text/plain", produces = "text/plain") + public String delete(@RequestBody String argument) throws IOException { + LOG.debug("Delete argument: [" + argument + "]"); + database.deleteArgument(argument); + return "OK\n"; + } + + @RequestMapping(path = "/check", method = RequestMethod.GET, + consumes = "text/plain", produces = "text/plain") + public String check(@RequestBody String argument) throws IOException { + LOG.debug("Check for existence of [" + argument + "] element"); + Double answer = database.loadMeaning(argument); + return answer.toString(); + } + + @RequestMapping(path = "/addUser/{variableName}", method = RequestMethod.POST, + consumes = "text/plain", produces = "text/plain") + public String addUser(Authentication authentication, @RequestBody String data) { + LOG.debug("Add new user [" + data + "]"); + String author = authentication.getName(); + if (author.equals("admin")) { + clients.addUser(data); + } else { + return "Has no permission to add user!\n"; + } + return "OK\n"; + } + + @RequestMapping(path = "/deleteUser/{variableName}", method = RequestMethod.DELETE, + consumes = "text/plain", produces = "text/plain") + public String deleteUser(Authentication authentication, @RequestBody String data) { + LOG.debug("Delete user [" + data + "]"); + String author = authentication.getName(); + if (author.equals("admin")) { + clients.deleteUser(data); + } else { + return "Has no permission to delete user!\n"; + } + return "OK\n"; + } +} \ No newline at end of file diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/Database.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/Database.java new file mode 100644 index 000000000..feda0668c --- /dev/null +++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/Database.java @@ -0,0 +1,118 @@ +package ru.mipt.java2016.homework.g596.stepanova.task4; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; + +@Repository +public class Database { + private static final Logger LOG = LoggerFactory.getLogger(Database.class); + + @Autowired private DataSource dataSource; + + private JdbcTemplate jdbcTemplate; + + @PostConstruct + public void postConstruct() { + jdbcTemplate = new JdbcTemplate(dataSource, false); + initSchema(); + } + + public void initSchema() { + LOG.trace("Initializing schema"); + jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS billing"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.users " + + "(username VARCHAR PRIMARY KEY, password VARCHAR, enabled BOOLEAN)"); + jdbcTemplate.update("DELETE FROM billing.users WHERE username = 'admin'"); + jdbcTemplate.update("INSERT INTO billing.users VALUES ('admin', 'admin', TRUE)"); + + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.arguments " + + "(argument VARCHAR PRIMARY KEY, meaning VARCHAR)"); + } + + + public BillingUser loadUser(String username) throws EmptyResultDataAccessException { + LOG.trace("Querying for user " + username); + return jdbcTemplate.queryForObject( + "SELECT username, password, enabled FROM billing.users WHERE username = ?", + new Object[] {username}, new RowMapper() { + @Override + public BillingUser mapRow(ResultSet rs, int rowNum) throws SQLException { + return new BillingUser(rs.getString("username"), rs.getString("password"), + rs.getBoolean("enabled")); + } + }); + } + + public void register(String[] tokens) throws ParsingException { + jdbcTemplate.update("DELETE FROM billing.arguments WHERE argument = '" + tokens[0] + "'"); + jdbcTemplate + .update("INSERT INTO billing.arguments VALUES('" + tokens[0] + "', '" + tokens[1] + + "')"); + } + + public double loadMeaning(String argument) throws IOException { + return jdbcTemplate.queryForObject( + "SELECT argument, meaning FROM billing.arguments WHERE argument = ?", + new Object[] {argument}, new RowMapper() { + @Override + public Double mapRow(ResultSet resultSet, int i) throws SQLException { + return resultSet.getDouble("meaning"); + } + }); + } + + public void deleteArgument(String argument) throws IOException { + jdbcTemplate.update("DELETE FROM billing.arguments WHERE argument = '" + argument + "'"); + } + + public double check(String argument) throws IOException { + int i = jdbcTemplate + .queryForObject("SELECT 1 COUNT * FROM billing.arguments WHERE argument = ?", + new Object[] {argument}, new RowMapper() { + @Override + public Integer mapRow(ResultSet resultSet, int i) throws SQLException { + return resultSet.getInt(1); + } + }); + if (i > 0) { + return jdbcTemplate.queryForObject( + "SELECT argument, meaning FROM billing.arguments WHERE argument = ?", + new Object[] {argument}, new RowMapper() { + @Override + public Double mapRow(ResultSet resultSet, int i) throws SQLException { + return resultSet.getDouble("meaning"); + } + }); + } + throw new IOException("Smth wen't wrong"); + } + + public void addUser(String data) { + String delim = "[;]"; + String[] tokens = data.split(delim); + jdbcTemplate.update("DELETE FROM billing.users WHERE username = '" + tokens[0] + "'"); + jdbcTemplate.update("INSERT INTO billing.users VALUES('" + tokens[0] + "', '" + tokens[1] + + "', TRUE)"); + } + + public void deleteUser(String data) { + jdbcTemplate.update("DELETE FROM billing.users WHERE username = '" + data + "'"); + } + + public boolean checkUser(String data) { + BillingUser user = loadUser(data); + return user.getUsername().length() > 0; + } +} \ No newline at end of file diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/MyApplication.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/MyApplication.java new file mode 100644 index 000000000..a4658fd94 --- /dev/null +++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/MyApplication.java @@ -0,0 +1,34 @@ +package ru.mipt.java2016.homework.g596.stepanova.task4; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import ru.mipt.java2016.homework.base.task1.Calculator; +import ru.mipt.java2016.homework.g596.stepanova.task1.MyCalc; + +@EnableAutoConfiguration +@Configuration +@ComponentScan(basePackageClasses = MyApplication.class) +public class MyApplication { + @Bean + public Calculator calculator() { + return new MyCalc(); + } + + @Bean + public EmbeddedServletContainerCustomizer customizer( + @Value("${ru.mipt.java2016.homework.g596.stepanova.task4.httpPort:9001}") int port) { + return container -> container.setPort(port); + } + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(MyApplication.class); + application.setBannerMode(Banner.Mode.OFF); + application.run(args); + } +} \ No newline at end of file diff --git a/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/SecurityServiceConfiguration.java b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/SecurityServiceConfiguration.java new file mode 100644 index 000000000..a540ec11a --- /dev/null +++ b/homework-g596-stepanova/src/main/java/ru/mipt/java2016/homework/g596/stepanova/task4/SecurityServiceConfiguration.java @@ -0,0 +1,46 @@ +package ru.mipt.java2016.homework.g596.stepanova.task4; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.Collections; + +@Configuration +@EnableWebSecurity +public class SecurityServiceConfiguration extends WebSecurityConfigurerAdapter { + private static final Logger LOG = LoggerFactory.getLogger(SecurityServiceConfiguration.class); + + @Autowired private Database clients; + + @Override + protected void configure(HttpSecurity http) throws Exception { + LOG.info("Configuring security"); + http.httpBasic().realmName("Calculator").and().formLogin().disable().logout().disable() + .csrf().disable().authorizeRequests().antMatchers("/eval/**").authenticated() + .anyRequest().permitAll(); + } + + @Autowired + public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception { + LOG.info("Registering global user details service"); + auth.userDetailsService(username -> { + try { + BillingUser user = clients.loadUser(username); + return new User(user.getUsername(), user.getPassword(), + Collections.singletonList(() -> "AUTH")); + } catch (EmptyResultDataAccessException e) { + LOG.warn("No such user: " + username); + throw new UsernameNotFoundException(username); + } + }); + } +} \ No newline at end of file