diff --git a/src/main/java/com/primefactorsolutions/invoices/config/SecurityConfig.java b/src/main/java/com/primefactorsolutions/invoices/config/SecurityConfig.java index 1e5c765..e771692 100644 --- a/src/main/java/com/primefactorsolutions/invoices/config/SecurityConfig.java +++ b/src/main/java/com/primefactorsolutions/invoices/config/SecurityConfig.java @@ -5,11 +5,11 @@ import com.vaadin.flow.spring.security.VaadinWebSecurity; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory; -import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @EnableWebSecurity @@ -25,13 +25,18 @@ public class SecurityConfig extends VaadinWebSecurity { setLoginView(http, LoginView.class); } - @Bean - public AuthenticationManager authenticationManager() { - DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://ldap.primefactorsolutions.com:389/dc=primefactorsolutions,dc=com"); - contextSource.setCacheEnvironmentProperties(false); - LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); - factory.setUserDnPatterns("uid={0},ou=users"); - return factory.createAuthenticationManager(); + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public DaoAuthenticationProvider authenticationProvider(final UserDetailsService userDetailsService) { + final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + + return authProvider; } } diff --git a/src/main/java/com/primefactorsolutions/invoices/data/UserRepository.java b/src/main/java/com/primefactorsolutions/invoices/data/UserRepository.java index cf30feb..8158d6b 100644 --- a/src/main/java/com/primefactorsolutions/invoices/data/UserRepository.java +++ b/src/main/java/com/primefactorsolutions/invoices/data/UserRepository.java @@ -6,4 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.UUID; public interface UserRepository extends JpaRepository { + User getByEmail(String email); } diff --git a/src/main/java/com/primefactorsolutions/invoices/model/Permission.java b/src/main/java/com/primefactorsolutions/invoices/model/Permission.java new file mode 100644 index 0000000..df1922d --- /dev/null +++ b/src/main/java/com/primefactorsolutions/invoices/model/Permission.java @@ -0,0 +1,13 @@ +package com.primefactorsolutions.invoices.model; + +import org.springframework.security.core.GrantedAuthority; + +public enum Permission implements GrantedAuthority { + INVALIDATE_INVOICE, + EDIT_COMPANY; + + @Override + public String getAuthority() { + return null; + } +} diff --git a/src/main/java/com/primefactorsolutions/invoices/model/Role.java b/src/main/java/com/primefactorsolutions/invoices/model/Role.java index ebe6489..c67a83c 100644 --- a/src/main/java/com/primefactorsolutions/invoices/model/Role.java +++ b/src/main/java/com/primefactorsolutions/invoices/model/Role.java @@ -1,6 +1,24 @@ package com.primefactorsolutions.invoices.model; -public enum Role { - ADMIN, - REGULAR +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; + +import java.util.List; + +@Getter +public enum Role implements GrantedAuthority { + ADMIN(List.of(Permission.EDIT_COMPANY, Permission.INVALIDATE_INVOICE)), + REGULAR(List.of()); + + private final List permissions; + + Role(final List permissions) { + this.permissions = permissions; + } + + @Override + public String getAuthority() { + return this.name(); + } + } diff --git a/src/main/java/com/primefactorsolutions/invoices/model/User.java b/src/main/java/com/primefactorsolutions/invoices/model/User.java index 6941977..bec73eb 100644 --- a/src/main/java/com/primefactorsolutions/invoices/model/User.java +++ b/src/main/java/com/primefactorsolutions/invoices/model/User.java @@ -1,25 +1,33 @@ package com.primefactorsolutions.invoices.model; -import jakarta.persistence.Entity; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; +import io.hypersistence.utils.hibernate.type.json.JsonType; +import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; @EqualsAndHashCode(callSuper = true) @Entity @Table(name = "user_") @Data -public class User extends AbstractEntity { +public class User extends AbstractEntity implements UserDetails { @NotBlank private String email; @NotBlank private String fullName; - private Role role; + @Type(JsonType.class) + @Column(columnDefinition = "json") + private List roles; + + private String password; @NotNull private Status status; @@ -28,4 +36,34 @@ public class User extends AbstractEntity { @JoinColumn(name = "company_id") @NotNull private Company company; + + @Override + public Collection getAuthorities() { + return this.roles.stream().flatMap(r -> r.getPermissions().stream()).toList(); + } + + @Override + public String getUsername() { + return this.email; + } + + @Override + public boolean isAccountNonExpired() { + return this.status != Status.DISABLED; + } + + @Override + public boolean isAccountNonLocked() { + return this.status != Status.DISABLED; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return this.status == Status.ACTIVE; + } } diff --git a/src/main/java/com/primefactorsolutions/invoices/services/ComponentBuilder.java b/src/main/java/com/primefactorsolutions/invoices/services/ComponentBuilder.java new file mode 100644 index 0000000..b73267b --- /dev/null +++ b/src/main/java/com/primefactorsolutions/invoices/services/ComponentBuilder.java @@ -0,0 +1,55 @@ +package com.primefactorsolutions.invoices.services; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.i18n.I18NProvider; +import lombok.Data; +import org.springframework.stereotype.Service; + +import java.util.Locale; + +@Service +@Data +public class ComponentBuilder { + private final I18NProvider i18NProvider; + + public ButtonBuilder button(String text) { + return ButtonBuilder.builder().i18NProvider(i18NProvider).text(text); + } + + @Data + public static class ButtonBuilder { + private String text; + private I18NProvider i18NProvider; + private ButtonVariant buttonVariant; + + public static ButtonBuilder builder() { + return new ButtonBuilder(); + } + + public Button build() { + final Button button = new Button(i18NProvider.getTranslation(text, Locale.of("es"))); + + if (buttonVariant != null) { + button.addThemeVariants(buttonVariant); + } + + return button; + } + + public ButtonBuilder primary() { + this.setButtonVariant(ButtonVariant.LUMO_PRIMARY); + return this; + } + + public ButtonBuilder i18NProvider(I18NProvider i18NProvider) { + this.setI18NProvider(i18NProvider); + return this; + } + + public ButtonBuilder text(String text) { + this.setText(text); + return this; + } + } +} diff --git a/src/main/java/com/primefactorsolutions/invoices/services/UserService.java b/src/main/java/com/primefactorsolutions/invoices/services/UserService.java index e96f5f0..19e4b9d 100644 --- a/src/main/java/com/primefactorsolutions/invoices/services/UserService.java +++ b/src/main/java/com/primefactorsolutions/invoices/services/UserService.java @@ -5,6 +5,9 @@ import com.primefactorsolutions.invoices.model.Role; import com.primefactorsolutions.invoices.model.Status; import com.primefactorsolutions.invoices.model.User; import lombok.Data; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; @@ -12,7 +15,7 @@ import java.util.UUID; @Service @Data -public class UserService { +public class UserService implements UserDetailsService { private final UserRepository userRepository; private final CompanyService companyService; @@ -28,10 +31,15 @@ public class UserService { if (updatedUser.getId() == null) { var company = companyService.getCompany(); updatedUser.setCompany(company); - updatedUser.setRole(Role.REGULAR); + updatedUser.setRoles(List.of(Role.REGULAR)); updatedUser.setStatus(Status.ACTIVE); } userRepository.save(updatedUser); } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return userRepository.getByEmail(username); + } } diff --git a/src/main/java/com/primefactorsolutions/invoices/views/ClientEditView.java b/src/main/java/com/primefactorsolutions/invoices/views/ClientEditView.java index 595c39a..7098e39 100644 --- a/src/main/java/com/primefactorsolutions/invoices/views/ClientEditView.java +++ b/src/main/java/com/primefactorsolutions/invoices/views/ClientEditView.java @@ -2,10 +2,9 @@ package com.primefactorsolutions.invoices.views; import com.primefactorsolutions.invoices.model.Client; import com.primefactorsolutions.invoices.services.ClientService; +import com.primefactorsolutions.invoices.services.ComponentBuilder; import com.primefactorsolutions.invoices.views.component.Breadcrumbs; import com.primefactorsolutions.invoices.views.component.GenericForm; -import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.i18n.I18NProvider; @@ -20,7 +19,6 @@ import org.apache.commons.lang3.tuple.Pair; import org.springframework.context.annotation.Scope; import java.util.List; -import java.util.Locale; import java.util.UUID; import static com.primefactorsolutions.invoices.utils.UiUtils.goTo; @@ -34,26 +32,26 @@ public class ClientEditView extends VerticalLayout implements HasUrlParameter clientGenericForm; - public ClientEditView(ClientService clientService, I18NProvider i18NProvider) { + public ClientEditView(ClientService clientService, I18NProvider i18NProvider, ComponentBuilder componentBuilder) { this.clientService = clientService; this.i18NProvider = i18NProvider; + this.componentBuilder = componentBuilder; + var client = new Client(); this.clientGenericForm = new GenericForm<>(Client.class); this.clientGenericForm.setBean(client); - String text = this.i18NProvider.getTranslation("action.save", Locale.of("es")); - var saveButton = new Button(text); - saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + var saveButton = this.componentBuilder.button("action.save").primary().build(); saveButton.addClickListener(c -> { var updatedClient = clientGenericForm.getBean(); clientService.saveOrUpdateClient(updatedClient); goTo(this, "clients"); }); - var cancelButton = new Button("Cancel"); + var cancelButton = this.componentBuilder.button("action.cancel").build(); cancelButton.addClickListener(c -> goTo(this, "clients")); var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Clientes", "clients"), Pair.of("Editar", null))); diff --git a/src/main/java/com/primefactorsolutions/invoices/views/ProductEditView.java b/src/main/java/com/primefactorsolutions/invoices/views/ProductEditView.java index 9b1608c..13adc62 100644 --- a/src/main/java/com/primefactorsolutions/invoices/views/ProductEditView.java +++ b/src/main/java/com/primefactorsolutions/invoices/views/ProductEditView.java @@ -18,6 +18,7 @@ import jakarta.annotation.security.PermitAll; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.context.annotation.Scope; +import org.vaadin.firitin.components.button.VButton; import java.util.List; import java.util.Locale; @@ -34,7 +35,6 @@ public class ProductEditView extends VerticalLayout implements HasUrlParameter productGenericForm; public ProductEditView(ProductService productService, I18NProvider i18NProvider) { @@ -54,9 +54,7 @@ public class ProductEditView extends VerticalLayout implements HasUrlParameter { - goTo(this, "products"); - }); + cancelButton.addClickListener(c -> goTo(this, "products")); var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Products", "products"), Pair.of("Editar", null))); var buttonLayout = new HorizontalLayout(saveButton, cancelButton); diff --git a/src/main/java/com/primefactorsolutions/invoices/views/FacturaComputarizadaComercialExportacionesServicioEditView.java b/src/main/java/com/primefactorsolutions/invoices/views/invoices/FacturaComputarizadaComercialExportacionesServicioEditView.java similarity index 98% rename from src/main/java/com/primefactorsolutions/invoices/views/FacturaComputarizadaComercialExportacionesServicioEditView.java rename to src/main/java/com/primefactorsolutions/invoices/views/invoices/FacturaComputarizadaComercialExportacionesServicioEditView.java index ce7778b..19c2b66 100644 --- a/src/main/java/com/primefactorsolutions/invoices/views/FacturaComputarizadaComercialExportacionesServicioEditView.java +++ b/src/main/java/com/primefactorsolutions/invoices/views/invoices/FacturaComputarizadaComercialExportacionesServicioEditView.java @@ -1,4 +1,4 @@ -package com.primefactorsolutions.invoices.views; +package com.primefactorsolutions.invoices.views.invoices; import com.primefactorsolutions.invoices.beans.CabeceraDTO; import com.primefactorsolutions.invoices.beans.ClientDTO; @@ -10,6 +10,7 @@ import com.primefactorsolutions.invoices.model.mappers.DetalleMapper; import com.primefactorsolutions.invoices.services.ClientService; import com.primefactorsolutions.invoices.services.CompanyService; import com.primefactorsolutions.invoices.services.InvoiceService; +import com.primefactorsolutions.invoices.views.MainLayout; import com.primefactorsolutions.invoices.views.component.invoice.*; import com.primefactorsolutions.invoices.xsd.FacturaComputarizadaComercialExportacionServicio; import com.vaadin.flow.component.*; diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 12ee0be..5dad280 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -14,5 +14,8 @@ INSERT INTO "PRODUCT" (ID, VERSION, COMPANY_ID, STATUS, codigo_Producto_Sin, cod precio_Unitario) VALUES ('dcecb825-7bc6-402d-850a-9d4a08fe6663', 1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 1, 1, 'PROD001', 'Test product', 1, 1); -INSERT INTO "USER_" (ID, VERSION, COMPANY_ID, EMAIL, FULL_NAME, ROLE, STATUS) VALUES ('b9f1bbe0-4542-48d3-b88e-452f2891cb68', -1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 'alex@test.com', 'alex prudencio', 1, 1); \ No newline at end of file +INSERT INTO "USER_" (ID, VERSION, COMPANY_ID, EMAIL, FULL_NAME, ROLES, PASSWORD, STATUS) VALUES ('b9f1bbe0-4542-48d3-b88e-452f2891cb68', +1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 'admin@test.com', 'admin test', '[0]' FORMAT JSON, '$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', 0); + +INSERT INTO "USER_" (ID, VERSION, COMPANY_ID, EMAIL, FULL_NAME, ROLES, PASSWORD, STATUS) VALUES ('547274a6-bb73-462d-85ab-b529fde798fb', +1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 'user@test.com', 'user test', '[1]' FORMAT JSON, '$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', 0); \ No newline at end of file diff --git a/src/main/resources/vaadin-i18n/translations.properties b/src/main/resources/vaadin-i18n/translations.properties index 21738a3..4701c69 100644 --- a/src/main/resources/vaadin-i18n/translations.properties +++ b/src/main/resources/vaadin-i18n/translations.properties @@ -1 +1,2 @@ -action.save=Save \ No newline at end of file +action.save=Save +action.cancel=Cancel \ No newline at end of file diff --git a/src/main/resources/vaadin-i18n/translations_es.properties b/src/main/resources/vaadin-i18n/translations_es.properties index 9104dba..df992df 100644 --- a/src/main/resources/vaadin-i18n/translations_es.properties +++ b/src/main/resources/vaadin-i18n/translations_es.properties @@ -1 +1,2 @@ -action.save=Guardar \ No newline at end of file +action.save=Guardar +action.cancel=Cancelar \ No newline at end of file diff --git a/src/test/java/com/primefactorsolutions/invoices/views/list/InvoiceFormTest.java b/src/test/java/com/primefactorsolutions/invoices/views/list/InvoiceFormTest.java index 1f1e140..b815f6f 100644 --- a/src/test/java/com/primefactorsolutions/invoices/views/list/InvoiceFormTest.java +++ b/src/test/java/com/primefactorsolutions/invoices/views/list/InvoiceFormTest.java @@ -1,12 +1,7 @@ package com.primefactorsolutions.invoices.views.list; -import com.primefactorsolutions.invoices.views.FacturaComputarizadaComercialExportacionesServicioEditView; -import com.vaadin.flow.component.Component; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.List; - public class InvoiceFormTest { @Test public void formShownWhenContactSelected() {