Implemented user management
This commit is contained in:
parent
f41e8fa6b2
commit
2475784d52
46 changed files with 1124 additions and 460 deletions
|
@ -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'
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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;
|
|
@ -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> {
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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();
|
|
@ -1,4 +1,4 @@
|
|||
package com.faendir.acra.data;
|
||||
package com.faendir.acra.mongod.model;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.faendir.acra.data;
|
||||
package com.faendir.acra.mongod.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
13
backend/src/main/resources/AppWidgetset.gwt.xml
Normal file
13
backend/src/main/resources/AppWidgetset.gwt.xml
Normal 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>
|
|
@ -1,6 +1,9 @@
|
|||
allprojects{
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = "http://maven.vaadin.com/vaadin-addons"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue