Implemented user management

This commit is contained in:
F43nd1r 2017-05-22 18:46:51 +02:00
parent f41e8fa6b2
commit 2475784d52
46 changed files with 1124 additions and 460 deletions

View file

@ -12,6 +12,7 @@ buildscript {
plugins {
id "net.ltgt.apt" version "0.10"
id 'fi.jasoft.plugin.vaadin' version '1.1.10'
}
apply plugin: 'java'
apply plugin: 'eclipse'
@ -20,7 +21,9 @@ apply plugin: 'org.springframework.boot'
group 'com.faendir'
version = '0.1.0-SNAPSHOT'
sourceCompatibility = 1.8
ext.debug = true;
ext {
debug = true
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-data-mongodb'
@ -31,17 +34,39 @@ dependencies {
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
compile 'commons-fileupload:commons-fileupload:1.3.2'
compile 'net.sf.proguard:proguard-retrace:5.3.3'
compile 'org.vaadin.addons:popupbutton:3.0.0'
compileOnly project(':annotation')
apt project(':annotationprocessor')
}
configurations {
'vaadin-client' {
resolutionStrategy {
dependencySubstitution {
substitute module('javax.validation:validation-api') with module('javax.validation:validation-api:1.0.0.GA')
}
//force 'javax.validation:validation-api:1.0.0.GA'
}
}
}
dependencyManagement {
imports {
mavenBom "com.vaadin:vaadin-bom:8.0.6"
}
}
if(!ext.debug){
bootRun {
// Needed by Vaadin plugin (https://github.com/johndevs/gradle-vaadin-plugin/issues/325)
classpath += configurations['vaadin-server']
classpath += configurations['vaadin-client']
dependsOn 'vaadinCompile', 'vaadinThemeCompile'
}
project.convention.getPlugin(WarPluginConvention).webAppDirName = "src/main/resources"
if (!ext.debug) {
apply from: 'releasepackaging.gradle'
}

View file

@ -1,38 +0,0 @@
package com.faendir.acra.data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import java.security.SecureRandom;
import java.util.List;
/**
* @author Lukas
* @since 22.03.2017
*/
@Component
public class AppManager {
private final SecureRandom secureRandom;
private final AppRepository appRepository;
@Autowired
public AppManager(SecureRandom secureRandom, AppRepository appRepository) {
this.secureRandom = secureRandom;
this.appRepository = appRepository;
}
public App createNewApp(String name){
byte[] bytes = new byte[12];
secureRandom.nextBytes(bytes);
return appRepository.save(new App(name, Base64Utils.encodeToString(bytes)));
}
public List<App> getApps(){
return appRepository.findAll();
}
public App getApp(String id){
return appRepository.findOne(id);
}
}

View file

@ -1,10 +0,0 @@
package com.faendir.acra.data;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Lukas
* @since 22.03.2017
*/
public interface AppRepository extends MongoRepository<App, String> {
}

View file

@ -1,49 +0,0 @@
package com.faendir.acra.data;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.gridfs.GridFSDBFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
/**
* @author Lukas
* @since 19.05.2017
*/
@Component
public class AttachmentManager {
private final GridFsTemplate gridFsTemplate;
private final Logger logger;
@Autowired
public AttachmentManager(GridFsTemplate gridFsTemplate) {
this.gridFsTemplate = gridFsTemplate;
logger = LoggerFactory.getLogger(AttachmentManager.class);
}
public void saveAttachments(String report, List<MultipartFile> attachments) {
for (MultipartFile a : attachments) {
try {
gridFsTemplate.store(a.getInputStream(), a.getOriginalFilename(), a.getContentType(), new BasicDBObjectBuilder().add("reportId", report).get());
} catch (IOException e) {
logger.warn("Failed to load attachment", e);
}
}
}
public List<GridFSDBFile> getAttachments(String report) {
return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report)));
}
public void removeAttachments(String report) {
gridFsTemplate.delete(new Query(Criteria.where("metadata.reportId").is(report)));
}
}

View file

@ -1,34 +0,0 @@
package com.faendir.acra.data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Lukas
* @since 19.05.2017
*/
@Component
public class MappingManager {
private final MappingRepository mappingRepository;
@Autowired
public MappingManager(MappingRepository mappingRepository) {
this.mappingRepository = mappingRepository;
}
public void addMapping(String app, int version, String mappings) {
mappingRepository.save(new ProguardMapping(app, version, mappings));
}
public ProguardMapping getMapping(String app, int version) {
return mappingRepository.findOne(new ProguardMapping.MetaData(app, version));
}
public List<ProguardMapping> getMappings(String app) {
return mappingRepository.findAll(Example.of(new ProguardMapping(app, -1, null), ExampleMatcher.matchingAny()));
}
}

View file

@ -1,10 +0,0 @@
package com.faendir.acra.data;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Lukas
* @since 19.05.2017
*/
public interface MappingRepository extends MongoRepository<ProguardMapping, ProguardMapping.MetaData> {
}

View file

@ -1,66 +0,0 @@
package com.faendir.acra.data;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Lukas
* @since 22.03.2017
*/
@Component
public class ReportManager {
private final ReportRepository reportRepository;
private final AttachmentManager attachmentManager;
private final List<ChangeListener> listeners;
@Autowired
public ReportManager(ReportRepository reportRepository, AttachmentManager attachmentManager) {
this.reportRepository = reportRepository;
this.attachmentManager = attachmentManager;
listeners = new ArrayList<>();
}
public void newReport(JSONObject content) {
newReport(content, Collections.emptyList());
}
public void newReport(JSONObject content, List<MultipartFile> attachments) {
Report report = reportRepository.save(new Report(content, SecurityContextHolder.getContext().getAuthentication().getName()));
attachmentManager.saveAttachments(report.getId(), attachments);
listeners.forEach(ChangeListener::onChange);
}
public List<Report> getReports(String app) {
return reportRepository.findAll(Example.of(new Report(null, app)));
}
public Report getReport(String id) {
return reportRepository.findOne(id);
}
public void remove(Report report){
reportRepository.delete(report);
attachmentManager.removeAttachments(report.getId());
listeners.forEach(ChangeListener::onChange);
}
public boolean addListener(ChangeListener changeListener) {
return listeners.add(changeListener);
}
public boolean removeListener(ChangeListener changeListener) {
return listeners.remove(changeListener);
}
public interface ChangeListener {
void onChange();
}
}

View file

@ -1,10 +0,0 @@
package com.faendir.acra.data;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Lukas
* @since 22.03.2017
*/
public interface ReportRepository extends MongoRepository<Report, String> {
}

View file

@ -1,4 +1,4 @@
package com.faendir.acra.data;
package com.faendir.acra.mongod;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View file

@ -0,0 +1,11 @@
package com.faendir.acra.mongod.data;
import com.faendir.acra.mongod.model.App;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Lukas
* @since 22.03.2017
*/
interface AppRepository extends MongoRepository<App, String> {
}

View file

@ -0,0 +1,138 @@
package com.faendir.acra.mongod.data;
import com.faendir.acra.mongod.model.App;
import com.faendir.acra.mongod.model.ProguardMapping;
import com.faendir.acra.mongod.model.Report;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.gridfs.GridFSDBFile;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Lukas
* @since 21.05.2017
*/
@Component
public class DataManager {
private final MappingRepository mappingRepository;
private final ReportRepository reportRepository;
private final List<ReportChangeListener> listeners;
private final GridFsTemplate gridFsTemplate;
private final Logger logger;
private final SecureRandom secureRandom;
private final AppRepository appRepository;
@Autowired
public DataManager(SecureRandom secureRandom, AppRepository appRepository, GridFsTemplate gridFsTemplate, MappingRepository mappingRepository, ReportRepository reportRepository) {
this.secureRandom = secureRandom;
this.appRepository = appRepository;
logger = LoggerFactory.getLogger(DataManager.class);
this.gridFsTemplate = gridFsTemplate;
this.mappingRepository = mappingRepository;
this.reportRepository = reportRepository;
this.listeners = new ArrayList<>();
}
public void createNewApp(String name){
byte[] bytes = new byte[12];
secureRandom.nextBytes(bytes);
appRepository.save(new App(name, Base64Utils.encodeToString(bytes)));
}
public List<App> getApps(){
return appRepository.findAll();
}
public App getApp(String id){
return appRepository.findOne(id);
}
public void deleteApp(String id){
appRepository.delete(id);
getReports(id).forEach(this::remove);
mappingRepository.delete(getMappings(id));
}
public void saveAttachments(String report, List<MultipartFile> attachments) {
for (MultipartFile a : attachments) {
try {
gridFsTemplate.store(a.getInputStream(), a.getOriginalFilename(), a.getContentType(), new BasicDBObjectBuilder().add("reportId", report).get());
} catch (IOException e) {
logger.warn("Failed to load attachment", e);
}
}
}
public List<GridFSDBFile> getAttachments(String report) {
return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report)));
}
public void removeAttachments(String report) {
gridFsTemplate.delete(new Query(Criteria.where("metadata.reportId").is(report)));
}
public void addMapping(String app, int version, String mappings) {
mappingRepository.save(new ProguardMapping(app, version, mappings));
}
public ProguardMapping getMapping(String app, int version) {
return mappingRepository.findOne(new ProguardMapping.MetaData(app, version));
}
public List<ProguardMapping> getMappings(String app) {
return mappingRepository.findAll(Example.of(new ProguardMapping(app, -1, null), ExampleMatcher.matchingAny()));
}
public void newReport(JSONObject content) {
newReport(content, Collections.emptyList());
}
public void newReport(JSONObject content, List<MultipartFile> attachments) {
Report report = reportRepository.save(new Report(content, SecurityContextHolder.getContext().getAuthentication().getName()));
saveAttachments(report.getId(), attachments);
listeners.forEach(ReportChangeListener::onChange);
}
public List<Report> getReports(String app) {
return reportRepository.findAll(Example.of(new Report(null, app)));
}
public Report getReport(String id) {
return reportRepository.findOne(id);
}
public void remove(Report report){
reportRepository.delete(report);
removeAttachments(report.getId());
listeners.forEach(ReportChangeListener::onChange);
}
public boolean addListener(ReportChangeListener reportChangeListener) {
return listeners.add(reportChangeListener);
}
public boolean removeListener(ReportChangeListener reportChangeListener) {
return listeners.remove(reportChangeListener);
}
public interface ReportChangeListener {
void onChange();
}
}

View file

@ -0,0 +1,11 @@
package com.faendir.acra.mongod.data;
import com.faendir.acra.mongod.model.ProguardMapping;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Lukas
* @since 19.05.2017
*/
interface MappingRepository extends MongoRepository<ProguardMapping, ProguardMapping.MetaData> {
}

View file

@ -0,0 +1,11 @@
package com.faendir.acra.mongod.data;
import com.faendir.acra.mongod.model.Report;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Lukas
* @since 22.03.2017
*/
interface ReportRepository extends MongoRepository<Report, String> {
}

View file

@ -1,5 +1,8 @@
package com.faendir.acra.data;
package com.faendir.acra.mongod.data;
import com.faendir.acra.mongod.model.Bug;
import com.faendir.acra.mongod.model.ProguardMapping;
import com.faendir.acra.mongod.model.Report;
import org.apache.commons.io.FileUtils;
import proguard.retrace.ReTrace;
@ -35,7 +38,7 @@ public final class ReportUtils {
return bugs;
}
static Date getDateFromString(String s) {
public static Date getDateFromString(String s) {
try {
return dateFormat.parse(s);
} catch (ParseException e) {
@ -43,7 +46,7 @@ public final class ReportUtils {
}
}
static String retrace(String stacktrace, ProguardMapping mapping) throws IOException {
public static String retrace(String stacktrace, ProguardMapping mapping) throws IOException {
File file = File.createTempFile("mapping", ".txt");
FileUtils.writeStringToFile(file, mapping.getMappings());
StringWriter writer = new StringWriter();

View file

@ -1,4 +1,4 @@
package com.faendir.acra.data;
package com.faendir.acra.mongod.model;
import org.springframework.data.mongodb.core.mapping.Document;

View file

@ -1,4 +1,4 @@
package com.faendir.acra.data;
package com.faendir.acra.mongod.model;
import java.util.ArrayList;
import java.util.Date;

View file

@ -0,0 +1,60 @@
package com.faendir.acra.mongod.model;
import org.springframework.security.core.GrantedAuthority;
/**
* @author Lukas
* @since 20.05.2017
*/
public class Permission implements GrantedAuthority {
public enum Level {
NONE,
VIEW,
EDIT,
ADMIN
}
private Level level;
private String app;
public Permission() {
}
public Permission(String app, Level level) {
this.level = level;
this.app = app;
}
public Level getLevel() {
return level;
}
public void setLevel(Level level) {
this.level = level;
}
public String getApp() {
return app;
}
@Override
public String getAuthority() {
return "PERMISSION_" + level.name() + "_" + app;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Permission that = (Permission) o;
return app.equals(that.app);
}
@Override
public int hashCode() {
return app.hashCode();
}
}

View file

@ -1,4 +1,4 @@
package com.faendir.acra.data;
package com.faendir.acra.mongod.model;
import org.springframework.data.mongodb.core.mapping.Document;
@ -33,11 +33,11 @@ public class ProguardMapping {
return mappings;
}
static class MetaData implements Serializable {
public static class MetaData implements Serializable {
private String app;
private int version;
MetaData(String app, int version) {
public MetaData(String app, int version) {
this.app = app;
this.version = version;
}

View file

@ -1,5 +1,7 @@
package com.faendir.acra.data;
package com.faendir.acra.mongod.model;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.data.ReportUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.data.mongodb.core.index.Indexed;
@ -47,11 +49,11 @@ public class Report {
return getValueSafe("STACK_TRACE", content::getString, "");
}
public String getDeObfuscatedStacktrace(MappingManager mappingManager){
public String getDeObfuscatedStacktrace(DataManager dataManager){
if (deObfuscatedTrace != null) {
return deObfuscatedTrace;
}
ProguardMapping mapping = mappingManager.getMapping(app, getVersionCode());
ProguardMapping mapping = dataManager.getMapping(app, getVersionCode());
if (mapping != null) {
try {
deObfuscatedTrace = ReportUtils.retrace(getStacktrace(), mapping);

View file

@ -0,0 +1,88 @@
package com.faendir.acra.mongod.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Lukas
* @since 20.05.2017
*/
@Document
public class User implements UserDetails {
@Id
private String username;
private String password;
private Set<String> roles;
private Set<Permission> permissions;
public User() {
permissions = new HashSet<>();
roles = new HashSet<>();
}
public User(String username, String password, Collection<String> roles) {
this();
this.username = username;
this.password = password;
this.roles = new HashSet<>(roles);
}
public Set<Permission> getPermissions() {
return permissions;
}
public Set<String> getRoles() {
return roles;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>(permissions);
authorities.addAll(roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View file

@ -0,0 +1,113 @@
package com.faendir.acra.mongod.user;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.Permission;
import com.faendir.acra.mongod.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
* @author Lukas
* @since 20.05.2017
*/
@Component
public class UserManager {
public static final String ROLE_ADMIN = "ROLE_ADMIN";
public static final String ROLE_USER = "ROLE_USER";
private final UserRepository userRepository;
private final DataManager dataManager;
private final String defaultUser;
private final String defaultPassword;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserManager(UserRepository userRepository, DataManager dataManager, @Value("${security.user.name}") String defaultUser, @Value("${security.user.password}") String defaultPassword) {
this.userRepository = userRepository;
this.dataManager = dataManager;
this.defaultUser = defaultUser;
this.defaultPassword = defaultPassword;
passwordEncoder = new BCryptPasswordEncoder();
}
private void ensureValidPermissions(User user) {
user.getPermissions().removeIf(permission -> dataManager.getApp(permission.getApp()) == null);
dataManager.getApps().stream().filter(app -> user.getPermissions().stream().noneMatch(permission -> permission.getApp().equals(app.getId())))
.forEach(app -> user.getPermissions().add(new Permission(app.getId(), user.getRoles().contains(ROLE_ADMIN) ? Permission.Level.ADMIN : Permission.Level.NONE)));
}
public User getUser(String username) {
User user = userRepository.findOne(username);
if (user == null && defaultUser.equals(username)) {
return getDefaultUser();
}
ensureValidPermissions(user);
return user;
}
public boolean createUser(String username, String password) {
if (userRepository.exists(username)) {
return false;
}
User user = new User(username, passwordEncoder.encode(password), Collections.singleton(ROLE_USER));
ensureValidPermissions(user);
userRepository.save(user);
return true;
}
public boolean checkPassword(User user, String password) {
return user != null && passwordEncoder.matches(password, user.getPassword());
}
public boolean changePassword(User user, String oldPassword, String newPassword) {
if (checkPassword(user, oldPassword)) {
user.setPassword(newPassword);
userRepository.save(user);
return true;
}
return false;
}
public void setAdmin(User user, boolean admin) {
if (admin) user.getRoles().add(ROLE_ADMIN);
else user.getRoles().remove(ROLE_ADMIN);
userRepository.save(user);
}
public User setPermission(User user, String app, Permission.Level level) {
Optional<Permission> permission = user.getPermissions().stream().filter(p -> p.getApp().equals(app)).findAny();
if (permission.isPresent()) {
permission.get().setLevel(level);
} else {
user.getPermissions().add(new Permission(app, level));
}
return userRepository.save(user);
}
public boolean hasPermission(User user, String app, Permission.Level level) {
ensureValidPermissions(user);
Optional<Permission> optional = user.getPermissions().stream().filter(permission -> permission.getApp().equals(app)).findAny();
return optional.isPresent() && optional.get().getLevel().ordinal() >= level.ordinal();
}
public List<User> getUsers() {
List<User> users = userRepository.findAll();
if (users.stream().noneMatch(user -> user.getUsername().equals(defaultUser))) {
users.add(getDefaultUser());
}
users.forEach(this::ensureValidPermissions);
return users;
}
private User getDefaultUser() {
return new User(defaultUser, passwordEncoder.encode(defaultPassword), Arrays.asList(ROLE_USER, ROLE_ADMIN));
}
}

View file

@ -0,0 +1,11 @@
package com.faendir.acra.mongod.user;
import com.faendir.acra.mongod.model.User;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Lukas
* @since 20.05.2017
*/
interface UserRepository extends MongoRepository<User, String> {
}

View file

@ -1,24 +0,0 @@
package com.faendir.acra.security;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @author Lukas
* @since 13.05.2017
*/
public class SecurityInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[0];
}
@Override
protected String[] getServletMappings() {
return new String[0];
}
}

View file

@ -1,5 +1,7 @@
package com.faendir.acra.security;
import com.faendir.acra.mongod.model.Permission;
import com.faendir.acra.mongod.user.UserManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
@ -18,4 +20,16 @@ public final class SecurityUtils {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && authentication.getAuthorities().contains(new SimpleGrantedAuthority(role));
}
public static String getUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null ? authentication.getName() : "";
}
public static boolean hasPermission(String app, Permission.Level level) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && authentication.getAuthorities().stream().filter(authority -> authority instanceof Permission)
.map(Permission.class::cast).filter(permission -> permission.getApp().equals(app)).findAny().map(Permission::getLevel)
.orElseGet(() -> hasRole(UserManager.ROLE_ADMIN) ? Permission.Level.ADMIN : Permission.Level.NONE).ordinal() >= level.ordinal();
}
}

View file

@ -1,11 +1,12 @@
package com.faendir.acra.security;
import com.faendir.acra.data.App;
import com.faendir.acra.data.AppManager;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.App;
import com.faendir.acra.mongod.model.User;
import com.faendir.acra.mongod.user.UserManager;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
@ -20,12 +21,15 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Collection;
/**
* @author Lukas
* @since 22.03.2017
@ -39,57 +43,62 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
SecurityContextHolder.setStrategyName(VaadinSessionSecurityContextHolderStrategy.class.getName());
}
private final String user;
private final String password;
private final AppManager appManager;
private final AuthenticationProvider authenticationProvider = new AuthenticationProvider() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication instanceof UsernamePasswordAuthenticationToken) {
if (user.equals(authentication.getName())) {
if (password.equals(authentication.getCredentials())) {
return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
} else {
throw new BadCredentialsException("Password mismatch for user " + authentication.getName());
}
}
App app = appManager.getApp(authentication.getName());
if (app != null) {
if (app.getPassword().equals(authentication.getCredentials())) {
Authentication auth = new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), AuthorityUtils.createAuthorityList("ROLE_REPORTER"));
VaadinSession session = VaadinSession.getCurrent();
if (session == null) session = new VaadinSession(VaadinService.getCurrent());
VaadinSession.setCurrent(session);
SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication(auth);
session.setAttribute(SecurityContext.class, securityContext);
return auth;
} else {
throw new BadCredentialsException("Password mismatch for user " + authentication.getName());
}
} else {
throw new UsernameNotFoundException("Username " + authentication.getName() + " not found");
}
}
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
};
private final DataManager dataManager;
private final UserManager userManager;
@Autowired
public WebSecurityConfig(@Value("${security.user.name}") String user, @Value("${security.user.password}") String password, AppManager appManager) {
this.user = user;
this.password = password;
this.appManager = appManager;
public WebSecurityConfig(DataManager dataManager, UserManager userManager) {
this.dataManager = dataManager;
this.userManager = userManager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.authenticationProvider(new AuthenticationProvider() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication instanceof UsernamePasswordAuthenticationToken) {
App app = dataManager.getApp(authentication.getName());
User user;
if (app != null) {
if (app.getPassword().equals(authentication.getCredentials())) {
return getGrantedToken(app.getId(), app.getPassword(), AuthorityUtils.createAuthorityList("ROLE_REPORTER"));
} else {
throwBadCredentials(app.getId());
}
} else if ((user = userManager.getUser(authentication.getName())) != null) {
if (userManager.checkPassword(user, (String) authentication.getCredentials())) {
return getGrantedToken(user.getUsername(), user.getPassword(), user.getAuthorities());
} else {
throwBadCredentials(user.getUsername());
}
} else {
throw new UsernameNotFoundException("Username " + authentication.getName() + " not found");
}
}
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
});
}
private void throwBadCredentials(String username) {
throw new BadCredentialsException("Password mismatch for user " + username);
}
private Authentication getGrantedToken(String username, String password, Collection<? extends GrantedAuthority> authorities) {
Authentication auth = new UsernamePasswordAuthenticationToken(username, password, authorities);
VaadinSession session = VaadinSession.getCurrent();
if (session == null) session = new VaadinSession(VaadinService.getCurrent());
VaadinSession.setCurrent(session);
SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication(auth);
session.setAttribute(SecurityContext.class, securityContext);
return auth;
}
@Override

View file

@ -1,6 +1,6 @@
package com.faendir.acra.service;
import com.faendir.acra.data.ReportManager;
import com.faendir.acra.mongod.data.DataManager;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
@ -26,18 +26,18 @@ import java.util.List;
*/
@RestController
public class ReportService {
private final ReportManager reportManager;
private final DataManager dataManager;
@Autowired
public ReportService(ReportManager reportManager) {
this.reportManager = reportManager;
public ReportService(DataManager dataManager) {
this.dataManager = dataManager;
}
@PreAuthorize("hasRole('REPORTER')")
@RequestMapping(value = "/report", consumes = MediaType.APPLICATION_JSON_VALUE)
public void report(@RequestBody String content) throws IOException {
JSONObject jsonObject = new JSONObject(content);
reportManager.newReport(jsonObject);
dataManager.newReport(jsonObject);
}
@PreAuthorize("hasRole('REPORTER')")
@ -56,7 +56,7 @@ public class ReportService {
}
}
if(jsonObject != null) {
reportManager.newReport(jsonObject, attachments);
dataManager.newReport(jsonObject, attachments);
return ResponseEntity.ok().build();
}else {
return ResponseEntity.badRequest().build();

View file

@ -1,12 +1,16 @@
package com.faendir.acra.ui;
import com.faendir.acra.mongod.user.UserManager;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.view.user.ChangePasswordView;
import com.faendir.acra.ui.view.user.UserManagerView;
import com.faendir.acra.util.Style;
import com.vaadin.annotations.Theme;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.spring.annotation.SpringUI;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.LoginForm;
@ -46,9 +50,7 @@ public class BackendUI extends UI {
if (SecurityUtils.isLoggedIn()) {
showMain();
} else {
LoginForm loginForm = new LoginForm();
loginForm.addLoginListener(event -> login(event.getLoginParameter("username"), event.getLoginParameter("password")));
setContent(loginForm);
showLogin();
}
}
@ -64,10 +66,43 @@ public class BackendUI extends UI {
}
}
public void logout() {
SecurityContextHolder.clearContext();
getPage().reload();
getSession().close();
}
private void showLogin() {
LoginForm loginForm = new LoginForm();
loginForm.addLoginListener(event -> login(event.getLoginParameter("username"), event.getLoginParameter("password")));
VerticalLayout layout = new VerticalLayout(loginForm);
layout.setSizeFull();
layout.setComponentAlignment(loginForm, Alignment.MIDDLE_CENTER);
setContent(layout);
}
private void showMain() {
NavigationManager navigationManager = applicationContext.getBean(NavigationManager.class);
HorizontalLayout header = new HorizontalLayout(new Button("Up", e -> navigationManager.navigateBack()));
Style.apply(header, Style.MARGIN_TOP, Style.MARGIN_LEFT, Style.MARGIN_RIGHT);
Button up = new Button("Up", e -> navigationManager.navigateBack());
HorizontalLayout header = new HorizontalLayout(up);
header.setExpandRatio(up, 1);
header.setWidth(100, Unit.PERCENTAGE);
if(SecurityUtils.hasRole(UserManager.ROLE_ADMIN)){
Button userManager = new Button("User Manager", e -> navigationManager.navigateTo(UserManagerView.class, ""));
header.addComponent(userManager);
header.setComponentAlignment(userManager, Alignment.MIDDLE_RIGHT);
}
Button changePassword = new Button("Change Password", e-> navigationManager.navigateTo(ChangePasswordView.class, ""));
header.addComponent(changePassword);
header.setComponentAlignment(changePassword, Alignment.MIDDLE_RIGHT);
Button logout = new Button("Logout", e -> logout());
header.addComponent(logout);
header.setComponentAlignment(logout, Alignment.MIDDLE_RIGHT);
Style.apply(header, Style.PADDING_TOP, Style.PADDING_LEFT, Style.PADDING_RIGHT);
VerticalLayout root = new VerticalLayout(header, content);
root.setExpandRatio(content, 1);
root.setSizeFull();

View file

@ -1,7 +1,9 @@
package com.faendir.acra.ui;
import com.faendir.acra.gen.ViewDefinition;
import com.faendir.acra.ui.view.NamedView;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.view.ErrorView;
import com.faendir.acra.ui.view.base.NamedView;
import com.vaadin.navigator.Navigator;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewProvider;
@ -37,7 +39,7 @@ public class NavigationManager {
@Override
public String getViewName(String viewAndParameters) {
String name = viewAndParameters.split("/", 2)[0];
if (views.stream().map(applicationContext::getBean).map(NamedView.class::cast).map(NamedView::getName).anyMatch(name::equals))
if (views.stream().map(applicationContext::getBean).map(NamedView.class::cast).filter(view -> SecurityUtils.hasRole(view.requiredRole())).map(NamedView::getName).anyMatch(name::equals))
return name;
return null;
}
@ -47,6 +49,7 @@ public class NavigationManager {
return views.stream().map(applicationContext::getBean).map(NamedView.class::cast).filter(view -> view.getName().equals(viewName)).findAny().orElse(null);
}
});
navigator.setErrorView(ErrorView.class);
views = ViewDefinition.getViewClasses();
String target = Optional.ofNullable(ui.getPage().getLocation().getFragment()).orElse("").replace("!", "");
backStack.add(target);
@ -56,8 +59,10 @@ public class NavigationManager {
public void navigateTo(Class<? extends NamedView> namedView, String contentId) {
NamedView view = applicationContext.getBean(namedView);
String target = view.getName() + (contentId == null ? "" : "/" + contentId) + view.fragmentSuffix();
backStack.add(0, target);
navigator.navigateTo(target);
if (!backStack.get(0).equals(target)) {
backStack.add(0, target);
navigator.navigateTo(target);
}
}
public void navigateBack() {

View file

@ -1,9 +1,14 @@
package com.faendir.acra.ui.view;
import com.faendir.acra.data.App;
import com.faendir.acra.data.AppManager;
import com.faendir.acra.data.MappingManager;
import com.faendir.acra.data.ReportManager;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.App;
import com.faendir.acra.mongod.model.Permission;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.view.base.NamedView;
import com.faendir.acra.ui.view.base.ReportList;
import com.faendir.acra.ui.view.tabs.BugTab;
import com.faendir.acra.ui.view.tabs.DeObfuscationTab;
import com.faendir.acra.ui.view.tabs.PropertiesTab;
import com.faendir.acra.util.Style;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.UIScope;
@ -21,15 +26,11 @@ import org.springframework.stereotype.Component;
@Component
public class AppView extends NamedView {
private final AppManager appManager;
private final ReportManager reportManager;
private final MappingManager mappingManager;
private final DataManager dataManager;
@Autowired
public AppView(AppManager appManager, ReportManager reportManager, MappingManager mappingManager) {
this.appManager = appManager;
this.reportManager = reportManager;
this.mappingManager = mappingManager;
public AppView(DataManager dataManager) {
this.dataManager = dataManager;
}
@Override
@ -45,13 +46,16 @@ public class AppView extends NamedView {
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
String[] parameters = event.getParameters().split("/");
App app = appManager.getApp(parameters[0]);
App app = dataManager.getApp(parameters[0]);
VerticalLayout statistics = new VerticalLayout(new Label("Coming soon"));
statistics.setCaption("Statistics");
statistics.setSizeFull();
TabSheet tabSheet = new TabSheet(new BugTab(app.getId(), getNavigationManager(), reportManager),
new ReportList(app.getId(), getNavigationManager(), reportManager),
statistics, new DeObfuscationTab(app.getId(), mappingManager), new PropertiesTab(app));
TabSheet tabSheet = new TabSheet(new BugTab(app.getId(), getNavigationManager(), dataManager),
new ReportList(app.getId(), getNavigationManager(), dataManager),
statistics, new DeObfuscationTab(app.getId(), dataManager));
if(SecurityUtils.hasPermission(app.getId(), Permission.Level.ADMIN)){
tabSheet.addComponent(new PropertiesTab(app, dataManager, getNavigationManager()));
}
tabSheet.setSizeFull();
VerticalLayout content = new VerticalLayout(tabSheet);
content.setSizeFull();
@ -66,6 +70,7 @@ public class AppView extends NamedView {
}
}
}
tabSheet.addSelectedTabChangeListener(e -> getUI().getPage().setUriFragment(getName() + "/" + app.getId() + "/" + tabSheet.getSelectedTab().getCaption(), false));
tabSheet.addSelectedTabChangeListener(e -> getUI().getPage()
.setUriFragment(getName() + "/" + app.getId() + "/" + tabSheet.getSelectedTab().getCaption(), false));
}
}

View file

@ -0,0 +1,25 @@
package com.faendir.acra.ui.view;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
/**
* @author Lukas
* @since 21.05.2017
*/
public class ErrorView extends CustomComponent implements View {
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
Label label = new Label("This page does not exist or you do not have the permission to view it.");
VerticalLayout layout = new VerticalLayout(label);
layout.setComponentAlignment(label, Alignment.MIDDLE_CENTER);
layout.setSizeFull();
setSizeFull();
setCompositionRoot(layout);
}
}

View file

@ -1,8 +1,12 @@
package com.faendir.acra.ui.view;
import com.faendir.acra.data.App;
import com.faendir.acra.data.AppManager;
import com.faendir.acra.data.ReportManager;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.App;
import com.faendir.acra.mongod.model.Permission;
import com.faendir.acra.mongod.user.UserManager;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.view.base.MyGrid;
import com.faendir.acra.ui.view.base.NamedView;
import com.faendir.acra.util.Style;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.UIScope;
@ -12,10 +16,12 @@ import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.components.grid.FooterCell;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Lukas
* @since 23.03.2017
@ -24,14 +30,12 @@ import org.springframework.stereotype.Component;
@Component
public class Overview extends NamedView {
private final AppManager appManager;
private final ReportManager reportManager;
private final DataManager dataManager;
private MyGrid<App> grid;
@Autowired
public Overview(AppManager appManager, ReportManager reportManager) {
this.appManager = appManager;
this.reportManager = reportManager;
public Overview(DataManager dataManager) {
this.dataManager = dataManager;
}
private void addApp() {
@ -39,9 +43,9 @@ public class Overview extends NamedView {
TextField name = new TextField("Name");
Button create = new Button("Create");
create.addClickListener(e -> {
appManager.createNewApp(name.getValue());
dataManager.createNewApp(name.getValue());
window.close();
grid.setItems(appManager.getApps());
grid.setItems(getApps());
});
VerticalLayout layout = new VerticalLayout(name, create);
@ -50,17 +54,22 @@ public class Overview extends NamedView {
UI.getCurrent().addWindow(window);
}
private List<App> getApps(){
return dataManager.getApps().stream().filter(app -> SecurityUtils.hasPermission(app.getId(), Permission.Level.VIEW)).collect(Collectors.toList());
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
grid = new MyGrid<>("Apps", appManager.getApps());
grid = new MyGrid<>("Apps", getApps());
grid.setSizeFull();
Grid.Column column = grid.addColumn(App::getName, "Name");
grid.addColumn(app -> String.valueOf(reportManager.getReports(app.getId()).size()), "Reports");
FooterCell footerCell = grid.appendFooterRow().getCell(column.getId());
Button add = new Button("New App", e -> addApp());
add.setSizeFull();
footerCell.setComponent(add);
grid.setSelectionMode(Grid.SelectionMode.NONE);
grid.addColumn(App::getName, "Name");
grid.addColumn(app -> String.valueOf(dataManager.getReports(app.getId()).size()), "Reports");
VerticalLayout layout = new VerticalLayout(grid);
if(SecurityUtils.hasRole(UserManager.ROLE_ADMIN)){
Button add = new Button("New App", e -> addApp());
layout.addComponent(add);
}
Style.apply(layout, Style.NO_PADDING, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
setCompositionRoot(layout);
grid.addItemClickListener(e -> getNavigationManager().navigateTo(AppView.class, e.getItem().getId()));

View file

@ -1,24 +0,0 @@
package com.faendir.acra.ui.view;
import com.faendir.acra.data.App;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
/**
* @author Lukas
* @since 19.05.2017
*/
public class PropertiesTab extends VerticalLayout {
public PropertiesTab(App app) {
String location = UI.getCurrent().getPage().getLocation().toASCIIString();
location = location.substring(0, location.indexOf('#'));
addComponent(new Label(String.format("Required ACRA configuration:<br><code>formUri = \"%sreport\",<br>" +
"formUriBasicAuthLogin = \"%s\",<br>formUriBasicAuthPassword = \"%s\",<br>" +
"httpMethod = HttpSender.Method.POST,<br>reportType = HttpSender.Type.JSON</code>",
location, app.getId(), app.getPassword()), ContentMode.HTML));
setCaption("Properties");
setSizeFull();
}
}

View file

@ -1,37 +0,0 @@
package com.faendir.acra.ui.view;
import com.faendir.acra.data.Report;
import com.faendir.acra.data.ReportManager;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.util.StringUtils;
import com.vaadin.ui.renderers.ButtonRenderer;
/**
* @author Lukas
* @since 14.05.2017
*/
public class ReportList extends MyGrid<Report> implements ReportManager.ChangeListener {
private final String app;
private final ReportManager reportManager;
public ReportList(String app, NavigationManager navigationManager, ReportManager reportManager) {
super("Reports", reportManager.getReports(app));
this.app = app;
this.reportManager = reportManager;
setSizeFull();
addColumn(report -> StringUtils.distanceFromNowAsString(report.getDate()), "Date");
addColumn(report -> String.valueOf(report.getVersionCode()), "App Version");
addColumn(Report::getAndroidVersion, "Android Version");
addColumn(Report::getPhoneModel, "Device");
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
addColumn(report -> "Delete", new ButtonRenderer<>(e -> reportManager.remove(e.getItem())));
addItemClickListener(e -> navigationManager.navigateTo(ReportView.class, e.getItem().getId()));
addAttachListener(e -> reportManager.addListener(this));
addDetachListener(e -> reportManager.removeListener(this));
}
@Override
public void onChange() {
setItems(reportManager.getReports(app));
}
}

View file

@ -1,9 +1,8 @@
package com.faendir.acra.ui.view;
import com.faendir.acra.data.AttachmentManager;
import com.faendir.acra.data.MappingManager;
import com.faendir.acra.data.Report;
import com.faendir.acra.data.ReportManager;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.Report;
import com.faendir.acra.ui.view.base.NamedView;
import com.faendir.acra.util.Style;
import com.mongodb.gridfs.GridFSDBFile;
import com.vaadin.navigator.ViewChangeListener;
@ -35,15 +34,11 @@ import java.util.stream.Stream;
@org.springframework.stereotype.Component
public class ReportView extends NamedView {
private final ReportManager reportManager;
private final AttachmentManager attachmentManager;
private final MappingManager mappingManager;
private final DataManager dataManager;
@Autowired
public ReportView(ReportManager reportManager, AttachmentManager attachmentManager, MappingManager mappingManager) {
this.reportManager = reportManager;
this.attachmentManager = attachmentManager;
this.mappingManager = mappingManager;
public ReportView(DataManager dataManager) {
this.dataManager = dataManager;
}
private Stream<Component> getLayoutForEntry(String key, Object value) {
@ -81,8 +76,8 @@ public class ReportView extends NamedView {
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
Report report = reportManager.getReport(event.getParameters());
List<GridFSDBFile> attachmentList = attachmentManager.getAttachments(report.getId());
Report report = dataManager.getReport(event.getParameters());
List<GridFSDBFile> attachmentList = dataManager.getAttachments(report.getId());
HorizontalLayout attachments = new HorizontalLayout(attachmentList.stream().map(file -> {
Button button = new Button(file.getFilename());
new FileDownloader(new StreamResource(file::getInputStream, file.getFilename())).extend(button);
@ -92,7 +87,7 @@ public class ReportView extends NamedView {
summaryGrid.addComponents(new Label("Version", ContentMode.PREFORMATTED), new Label(report.getVersionName(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("Email", ContentMode.PREFORMATTED), new Label(report.getUserEmail(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("Comment", ContentMode.PREFORMATTED), new Label(report.getUserComment(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("De-obfuscated Stacktrace", ContentMode.PREFORMATTED), new Label(report.getDeObfuscatedStacktrace(mappingManager), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("De-obfuscated Stacktrace", ContentMode.PREFORMATTED), new Label(report.getDeObfuscatedStacktrace(dataManager), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("Attachments", ContentMode.PREFORMATTED), attachments);
summaryGrid.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
summaryGrid.setSizeFull();

View file

@ -1,4 +1,4 @@
package com.faendir.acra.ui.view;
package com.faendir.acra.ui.view.base;
import com.vaadin.data.ValueProvider;
import com.vaadin.ui.Grid;
@ -10,6 +10,10 @@ import java.util.Collection;
* @since 14.05.2017
*/
public class MyGrid<T> extends Grid<T> {
public MyGrid(String caption) {
super(caption);
}
public MyGrid(String caption, Collection<T> items) {
super(caption, items);
}

View file

@ -1,6 +1,7 @@
package com.faendir.acra.ui.view;
package com.faendir.acra.ui.view.base;
import com.faendir.acra.annotation.AutoDiscoverView;
import com.faendir.acra.mongod.user.UserManager;
import com.faendir.acra.ui.NavigationManager;
import com.vaadin.navigator.View;
import com.vaadin.ui.CustomComponent;
@ -23,7 +24,11 @@ public abstract class NamedView extends CustomComponent implements View {
return "";
}
NavigationManager getNavigationManager() {
public String requiredRole(){
return UserManager.ROLE_USER;
}
protected NavigationManager getNavigationManager() {
return navigationManager;
}

View file

@ -0,0 +1,43 @@
package com.faendir.acra.ui.view.base;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.Permission;
import com.faendir.acra.mongod.model.Report;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.ReportView;
import com.faendir.acra.util.StringUtils;
import com.vaadin.ui.renderers.ButtonRenderer;
/**
* @author Lukas
* @since 14.05.2017
*/
public class ReportList extends MyGrid<Report> implements DataManager.ReportChangeListener {
private final String app;
private final DataManager dataManager;
public ReportList(String app, NavigationManager navigationManager, DataManager dataManager) {
super("Reports", dataManager.getReports(app));
this.app = app;
this.dataManager = dataManager;
setSizeFull();
setSelectionMode(SelectionMode.NONE);
addColumn(report -> StringUtils.distanceFromNowAsString(report.getDate()), "Date");
addColumn(report -> String.valueOf(report.getVersionCode()), "App Version");
addColumn(Report::getAndroidVersion, "Android Version");
addColumn(Report::getPhoneModel, "Device");
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
if(SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
addColumn(report -> "Delete", new ButtonRenderer<>(e -> dataManager.remove(e.getItem())));
}
addItemClickListener(e -> navigationManager.navigateTo(ReportView.class, e.getItem().getId()));
addAttachListener(e -> dataManager.addListener(this));
addDetachListener(e -> dataManager.removeListener(this));
}
@Override
public void onChange() {
setItems(dataManager.getReports(app));
}
}

View file

@ -1,9 +1,11 @@
package com.faendir.acra.ui.view;
package com.faendir.acra.ui.view.tabs;
import com.faendir.acra.data.Bug;
import com.faendir.acra.data.ReportManager;
import com.faendir.acra.data.ReportUtils;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.Bug;
import com.faendir.acra.mongod.data.ReportUtils;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.base.MyGrid;
import com.faendir.acra.ui.view.base.ReportList;
import com.faendir.acra.util.StringUtils;
import com.faendir.acra.util.Style;
import com.vaadin.event.selection.SelectionEvent;
@ -14,19 +16,19 @@ import com.vaadin.ui.VerticalLayout;
* @author Lukas
* @since 17.05.2017
*/
public class BugTab extends CustomComponent implements ReportManager.ChangeListener {
public class BugTab extends CustomComponent implements DataManager.ReportChangeListener {
public static final String CAPTION = "Bugs";
private final VerticalLayout root;
private final String app;
private final NavigationManager navigationManager;
private final ReportManager reportManager;
private final DataManager dataManager;
private final MyGrid<Bug> bugs;
public BugTab(String app, NavigationManager navigationManager, ReportManager reportManager) {
public BugTab(String app, NavigationManager navigationManager, DataManager dataManager) {
this.app = app;
this.navigationManager = navigationManager;
this.reportManager = reportManager;
bugs = new MyGrid<>(null, ReportUtils.getBugs(reportManager.getReports(app)));
this.dataManager = dataManager;
bugs = new MyGrid<>(null, ReportUtils.getBugs(dataManager.getReports(app)));
bugs.setSizeFull();
bugs.addColumn(bug -> String.valueOf(bug.getReports().size()), "Reports");
bugs.addColumn(bug -> StringUtils.distanceFromNowAsString(bug.getLastDate()), "Latest Report");
@ -39,19 +41,19 @@ public class BugTab extends CustomComponent implements ReportManager.ChangeListe
setCompositionRoot(root);
setSizeFull();
setCaption(CAPTION);
addAttachListener(e -> reportManager.addListener(this));
addDetachListener(e -> reportManager.removeListener(this));
addAttachListener(e -> dataManager.addListener(this));
addDetachListener(e -> dataManager.removeListener(this));
}
private void handleBugSelection(SelectionEvent<Bug> e) {
if(root.getComponentCount() == 2){
root.removeComponent(root.getComponent(1));
}
e.getFirstSelectedItem().ifPresent(bug -> root.addComponent(new ReportList(app, navigationManager, reportManager)));
e.getFirstSelectedItem().ifPresent(bug -> root.addComponent(new ReportList(app, navigationManager, dataManager)));
}
@Override
public void onChange() {
bugs.setItems(ReportUtils.getBugs(reportManager.getReports(app)));
bugs.setItems(ReportUtils.getBugs(dataManager.getReports(app)));
}
}

View file

@ -1,8 +1,13 @@
package com.faendir.acra.ui.view;
package com.faendir.acra.ui.view.tabs;
import com.faendir.acra.data.MappingManager;
import com.faendir.acra.data.ProguardMapping;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.Permission;
import com.faendir.acra.mongod.model.ProguardMapping;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.view.base.MyGrid;
import com.faendir.acra.util.Style;
import com.vaadin.ui.Button;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
@ -17,21 +22,28 @@ import java.io.ByteArrayOutputStream;
* @author Lukas
* @since 19.05.2017
*/
public class DeObfuscationTab extends MyGrid<ProguardMapping> {
public class DeObfuscationTab extends CustomComponent {
private final String app;
private final MappingManager mappingManager;
private final DataManager dataManager;
private final MyGrid<ProguardMapping> grid;
private boolean validNumber;
private boolean validFile;
public DeObfuscationTab(String app, MappingManager mappingManager) {
super("De-Obfuscation", mappingManager.getMappings(app));
public DeObfuscationTab(String app, DataManager dataManager) {
setCaption("De-Obfuscation");
grid = new MyGrid<>(null, dataManager.getMappings(app));
this.app = app;
this.mappingManager = mappingManager;
Column column = addColumn(mapping -> String.valueOf(mapping.getVersion()), "Version");
setSizeFull();
Button add = new Button("Add File", e -> addFile());
add.setSizeFull();
appendFooterRow().getCell(column).setComponent(add);
this.dataManager = dataManager;
grid.addColumn(mapping -> String.valueOf(mapping.getVersion()), "Version");
grid.setSizeFull();
VerticalLayout layout = new VerticalLayout(grid);
layout.setSizeFull();
Style.NO_PADDING.apply(layout);
setCompositionRoot(layout);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
layout.addComponent(new Button("Add File", e -> addFile()));
}
}
private void addFile() {
@ -64,8 +76,8 @@ public class DeObfuscationTab extends MyGrid<ProguardMapping> {
});
upload.setSizeFull();
confirm.addClickListener(e -> {
mappingManager.addMapping(app, Integer.parseInt(version.getValue()), out.toString());
setItems(mappingManager.getMappings(app));
dataManager.addMapping(app, Integer.parseInt(version.getValue()), out.toString());
grid.setItems(dataManager.getMappings(app));
window.close();
});
confirm.setSizeFull();

View file

@ -0,0 +1,52 @@
package com.faendir.acra.ui.view.tabs;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.App;
import com.faendir.acra.ui.NavigationManager;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
/**
* @author Lukas
* @since 19.05.2017
*/
public class PropertiesTab extends VerticalLayout {
private final App app;
private final DataManager dataManager;
private final NavigationManager navigationManager;
public PropertiesTab(App app, DataManager dataManager, NavigationManager navigationManager) {
this.app = app;
this.dataManager = dataManager;
this.navigationManager = navigationManager;
String location = UI.getCurrent().getPage().getLocation().toASCIIString();
location = location.substring(0, location.indexOf('#'));
addComponent(new Label(String.format("Required ACRA configuration:<br><code>formUri = \"%sreport\",<br>" +
"formUriBasicAuthLogin = \"%s\",<br>formUriBasicAuthPassword = \"%s\",<br>" +
"httpMethod = HttpSender.Method.POST,<br>reportType = HttpSender.Type.JSON</code>",
location, app.getId(), app.getPassword()), ContentMode.HTML));
addComponent(new Button("Delete App", e -> deleteApp()));
setCaption("Properties");
setSizeUndefined();
}
private void deleteApp() {
Window window = new Window("Confirm");
Label label = new Label("Are you sure you want to delete this app and all its reports and mappings?");
Button yes = new Button("Yes", e -> {
dataManager.deleteApp(app.getId());
window.close();
navigationManager.navigateBack();
});
Button no = new Button("No", e -> window.close());
VerticalLayout layout = new VerticalLayout(label, new HorizontalLayout(yes, no));
window.setContent(layout);
window.center();
UI.getCurrent().addWindow(window);
}
}

View file

@ -0,0 +1,72 @@
package com.faendir.acra.ui.view.user;
import com.faendir.acra.mongod.model.User;
import com.faendir.acra.mongod.user.UserManager;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.BackendUI;
import com.faendir.acra.ui.view.base.NamedView;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.server.UserError;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Notification;
import com.vaadin.ui.PasswordField;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Lukas
* @since 21.05.2017
*/
@UIScope
@Component
public class ChangePasswordView extends NamedView {
private final UserManager userManager;
private final BackendUI backendUI;
@Autowired
public ChangePasswordView(UserManager userManager, BackendUI backendUI) {
this.userManager = userManager;
this.backendUI = backendUI;
}
@Override
public String getName() {
return "password-editor";
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
PasswordField oldPassword = new PasswordField("Old Password");
PasswordField newPassword = new PasswordField("New Password");
PasswordField repeatPassword = new PasswordField("Repeat Password");
Button confirm = new Button("Confirm", e -> {
User user = userManager.getUser(SecurityUtils.getUsername());
if(userManager.checkPassword(user, oldPassword.getValue())) {
if (newPassword.getValue().equals(repeatPassword.getValue())) {
user.setPassword(newPassword.getValue());
Notification.show("Successful!");
getNavigationManager().navigateBack();
backendUI.logout();
} else {
repeatPassword.setComponentError(new UserError("Passwords do not match"));
}
}else {
oldPassword.setComponentError(new UserError("Incorrect password"));
}
});
confirm.setSizeFull();
VerticalLayout layout = new VerticalLayout(oldPassword, newPassword, repeatPassword, confirm);
layout.setSizeUndefined();
VerticalLayout root = new VerticalLayout(layout);
root.setSizeFull();
root.setComponentAlignment(layout, Alignment.MIDDLE_CENTER);
setSizeFull();
setCompositionRoot(root);
}
}

View file

@ -0,0 +1,86 @@
package com.faendir.acra.ui.view.user;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.Permission;
import com.faendir.acra.ui.view.base.MyGrid;
import com.vaadin.data.HasValue;
import com.vaadin.shared.Registration;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Grid;
import org.vaadin.hene.popupbutton.PopupButton;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
/**
* @author Lukas
* @since 20.05.2017
*/
public class PermissionEditor extends PopupButton implements HasValue<Collection<Permission>> {
private Collection<Permission> items;
private MyGrid<Permission> grid;
public PermissionEditor(DataManager dataManager) {
super("Editor");
items = new ArrayList<>();
grid = new MyGrid<>(null, items);
grid.addColumn(permission -> dataManager.getApp(permission.getApp()).getName(), "App");
ComboBox<Permission.Level> levelComboBox = new ComboBox<>("", Arrays.asList(Permission.Level.values()));
grid.addColumn(permission -> permission.getLevel().name(), "Level")
.setEditorBinding(grid.getEditor().getBinder().bind(levelComboBox, Permission::getLevel, (permission, level) -> {
Collection<Permission> newValue = items.stream().map(p -> new Permission(p.getApp(), p.getLevel())).collect(Collectors.toList());
newValue.stream().filter(p -> p.getApp().equals(permission.getApp())).findAny().ifPresent(p -> p.setLevel(level));
setValue(newValue, true);
}));
grid.getEditor().setEnabled(true).setBuffered(false);
grid.setSelectionMode(Grid.SelectionMode.NONE);
setContent(grid);
setPopupVisible(true);
}
@Override
public void setValue(Collection<Permission> value) {
setValue(value, false);
}
private void setValue(Collection<Permission> value, boolean userOriginated) {
Collection<Permission> oldValue = items;
items = value;
grid.setItems(items);
grid.setHeightByRows(items.size());
fireEvent(new ValueChangeEvent<>(this, oldValue, userOriginated));
}
@Override
public Collection<Permission> getValue() {
return items;
}
@Override
public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) {
}
@Override
public boolean isRequiredIndicatorVisible() {
return false;
}
@Override
public Registration addValueChangeListener(ValueChangeListener<Collection<Permission>> listener) {
return addListener(ValueChangeEvent.class, listener,
ValueChangeListener.VALUE_CHANGE_METHOD);
}
@Override
public void setReadOnly(boolean readOnly) {
}
@Override
public boolean isReadOnly() {
return false;
}
}

View file

@ -0,0 +1,106 @@
package com.faendir.acra.ui.view.user;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.User;
import com.faendir.acra.mongod.user.UserManager;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.view.base.MyGrid;
import com.faendir.acra.ui.view.base.NamedView;
import com.faendir.acra.util.Style;
import com.vaadin.data.Binder;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.server.UserError;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Button;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Grid;
import com.vaadin.ui.PasswordField;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.stream.Collectors;
/**
* @author Lukas
* @since 20.05.2017
*/
@UIScope
@Component
public class UserManagerView extends NamedView {
private final UserManager userManager;
private final DataManager dataManager;
private MyGrid<User> userGrid;
@Autowired
public UserManagerView(UserManager userManager, DataManager dataManager) {
this.userManager = userManager;
this.dataManager = dataManager;
}
@Override
public String getName() {
return "user-manager";
}
@Override
public String requiredRole() {
return UserManager.ROLE_ADMIN;
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
userGrid = new MyGrid<>("Users", userManager.getUsers());
userGrid.getEditor().setEnabled(true).setBuffered(false);
userGrid.setSelectionMode(Grid.SelectionMode.NONE);
userGrid.addColumn(User::getUsername, "Username");
Binder<User> binder = userGrid.getEditor().getBinder();
userGrid.addColumn(user -> user.getRoles().contains(UserManager.ROLE_ADMIN) ? "Yes" : "No").setCaption("Admin")
.setEditorBinding(binder.forField(new CheckBox())
.withValidator(bool -> bool || !binder.getBean().getUsername().equals(SecurityUtils.getUsername()), "Cannot revoke own admin privileges")
.bind(user -> user.getRoles().contains(UserManager.ROLE_ADMIN), userManager::setAdmin));
userGrid.addColumn(this::getPermissionString, "App Permissions")
.setEditorBinding(userGrid.getEditor().getBinder().bind(new PermissionEditor(dataManager), User::getPermissions,
((user, permissions) -> permissions.forEach(p -> userManager.setPermission(user, p.getApp(), p.getLevel())))));
Button newUser = new Button("New User", e -> newUser());
VerticalLayout layout = new VerticalLayout(userGrid, newUser);
layout.setSizeFull();
Style.NO_PADDING.apply(layout);
setCompositionRoot(layout);
userGrid.setSizeFull();
setSizeFull();
Style.apply(this, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
}
private String getPermissionString(User user) {
return user.getPermissions().stream()
.map(permission -> dataManager.getApp(permission.getApp()).getName() + ": " + permission.getLevel().name())
.collect(Collectors.joining(", "));
}
private void newUser() {
Window window = new Window("New User");
TextField name = new TextField("Username");
PasswordField password = new PasswordField("Password");
PasswordField repeatPassword = new PasswordField("Repeat Password");
Button create = new Button("Create");
create.addClickListener(e -> {
if (password.getValue().equals(repeatPassword.getValue())) {
userManager.createUser(name.getValue(), password.getValue());
userGrid.setItems(userManager.getUsers());
window.close();
} else {
repeatPassword.setComponentError(new UserError("Passwords do not match"));
}
});
VerticalLayout layout = new VerticalLayout(name, password, repeatPassword, create);
window.setContent(layout);
window.center();
UI.getCurrent().addWindow(window);
}
}

View file

@ -30,8 +30,6 @@ public class Rfc1341ServletFileUpload extends ServletFileUpload {
/**
* Modified copy (with appropriate cast) of {@link FileUploadBase#parseRequest(RequestContext)}
*
* @inheritDoc
*/
@Override
public List<FileItem> parseRequest(RequestContext ctx) throws FileUploadException {

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Vaadin//DTD Vaadin 7//EN" "https://raw.github.com/vaadin/gwt/master/distro-source/core/src/gwt-module.dtd">
<!-- WS Compiler: manually edited -->
<module>
<inherits name="com.vaadin.DefaultWidgetSet" />
<inherits name="org.vaadin.hene.popupbutton.widgetset.PopupbuttonWidgetset" />
<set-configuration-property name="devModeRedirectEnabled" value="true" />
<set-property name="user.agent" value="ie8,ie9,gecko1_8,safari,ie10" />
<source path="client" />
<source path="shared" />
<collapse-all-properties />
<set-property name="compiler.useSymbolMaps" value="true" />
</module>

View file

@ -1,6 +1,9 @@
allprojects{
repositories {
mavenCentral()
maven {
url = "http://maven.vaadin.com/vaadin-addons"
}
}
}