diff --git a/2025-09/spring-23-ACL/ACL/pom.xml b/2025-09/spring-23-ACL/ACL/pom.xml new file mode 100644 index 00000000..2ba139f5 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + ru.otus + spring-framework-26-acl + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.6.10 + + + + 2.6.11 + 3.0.0 + UTF-8 + UTF-8 + 11 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.security + spring-security-acl + + + net.sf.ehcache + ehcache-core + ${ehcache-core.version} + jar + + + org.springframework + spring-context-support + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + + + + + + org.springdoc + springdoc-openapi-ui + 1.6.9 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/Main.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/Main.java new file mode 100644 index 00000000..baca49d5 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/Main.java @@ -0,0 +1,14 @@ +package ru.otus.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class); + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java new file mode 100644 index 00000000..e0363d94 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java @@ -0,0 +1,30 @@ +package ru.otus.spring.model; + +import javax.persistence.*; + +@Entity +@Table(name = "system_message") +public class NoticeMessage { + + @Id + @Column + private Integer id; + @Column + private String content; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java new file mode 100644 index 00000000..501d91a2 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java @@ -0,0 +1,21 @@ +package ru.otus.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; +import ru.otus.spring.model.NoticeMessage; + +import java.util.List; +import java.util.Optional; + +public interface NoticeMessageRepository extends JpaRepository { + + List findAll(); + + Optional findById(Integer id); + + NoticeMessage save(NoticeMessage noticeMessage); + +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java new file mode 100644 index 00000000..01bb4154 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java @@ -0,0 +1,39 @@ +package ru.otus.spring.rest; + +import org.springframework.web.bind.annotation.*; +import ru.otus.spring.model.NoticeMessage; +import ru.otus.spring.repository.NoticeMessageRepository; +import ru.otus.spring.service.NoticeService; + +import java.util.List; + +@RestController +public class NoticeMessageController { + + private final NoticeService noticeService; + + public NoticeMessageController(NoticeService noticeService) { + this.noticeService = noticeService; + } + + @GetMapping("/message") + public List getAll() { + return noticeService.getAll(); + } + + @GetMapping("/message/{id}") + public NoticeMessage get(@PathVariable("id") Integer id) { + var result = noticeService.get( id ); + return result; + } + + @PostMapping("/message") + public NoticeMessage createMessage(@RequestBody NoticeMessage message) { + return noticeService.create(message); + } + + @PutMapping("/message/{id}") + public NoticeMessage updateMessage(@PathVariable("id") Integer id, @RequestBody NoticeMessage message) { + return noticeService.update(message); + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclConfig.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclConfig.java new file mode 100644 index 00000000..d261e2a7 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclConfig.java @@ -0,0 +1,79 @@ +package ru.otus.spring.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.ehcache.EhCacheFactoryBean; +import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.acls.AclPermissionCacheOptimizer; +import org.springframework.security.acls.AclPermissionEvaluator; +import org.springframework.security.acls.domain.*; +import org.springframework.security.acls.jdbc.BasicLookupStrategy; +import org.springframework.security.acls.jdbc.JdbcMutableAclService; +import org.springframework.security.acls.jdbc.LookupStrategy; +import org.springframework.security.acls.model.PermissionGrantingStrategy; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import javax.sql.DataSource; +import java.util.Objects; + +@Configuration +public class AclConfig { + + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") + @Autowired + private DataSource dataSource; + + @Bean + public EhCacheBasedAclCache aclCache() { + return new EhCacheBasedAclCache( + Objects.requireNonNull(aclEhCacheFactoryBean().getObject()), + permissionGrantingStrategy(), + aclAuthorizationStrategy() + ); + } + + @Bean + public EhCacheFactoryBean aclEhCacheFactoryBean() { + EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean(); + ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject()); + ehCacheFactoryBean.setCacheName("aclCache"); + return ehCacheFactoryBean; + } + + @Bean + public EhCacheManagerFactoryBean aclCacheManager() { + return new EhCacheManagerFactoryBean(); + } + + @Bean + public PermissionGrantingStrategy permissionGrantingStrategy() { + return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()); + } + + @Bean + public AclAuthorizationStrategy aclAuthorizationStrategy() { + return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_EDITOR")); + } + + @Bean + public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() { + AclMethodSecurityExpressionHandler expressionHandler = new AclMethodSecurityExpressionHandler(); + AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService()); + expressionHandler.setPermissionEvaluator(permissionEvaluator); + expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService())); + return expressionHandler; + } + + @Bean + public LookupStrategy lookupStrategy() { + return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger()); + } + + @Bean + public JdbcMutableAclService aclService() { + return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache()); + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java new file mode 100644 index 00000000..5fb8a778 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java @@ -0,0 +1,29 @@ +package ru.otus.spring.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.acls.model.AclService; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class AclMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration { + + private final AclService aclService; + + public AclMethodSecurityConfiguration(AclService aclService) { + this.aclService = aclService; + } + + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") + @Autowired + MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler; + + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + return defaultMethodSecurityExpressionHandler; + } + +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionHandler.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionHandler.java new file mode 100644 index 00000000..ef30e5f3 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionHandler.java @@ -0,0 +1,23 @@ +package ru.otus.spring.security; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.core.Authentication; + +public class AclMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { + + @Override + protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, + MethodInvocation invocation) { + + AclMethodSecurityExpressionRoot root = new AclMethodSecurityExpressionRoot(authentication); + root.setThis(invocation.getThis()); + root.setPermissionEvaluator(this.getPermissionEvaluator()); + root.setTrustResolver(this.getTrustResolver()); + root.setRoleHierarchy(this.getRoleHierarchy()); + root.setDefaultRolePrefix(this.getDefaultRolePrefix()); + + return root; + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionOperations.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionOperations.java new file mode 100644 index 00000000..f7885ab0 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionOperations.java @@ -0,0 +1,12 @@ +package ru.otus.spring.security; + +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; + +public interface AclMethodSecurityExpressionOperations extends MethodSecurityExpressionOperations { + + boolean isAdministrator(Object targetId, Class targetClass); + + boolean isAdministrator(Object target); + + boolean canRead(Object targetId, Class targetClass); +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionRoot.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionRoot.java new file mode 100644 index 00000000..73cf1068 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionRoot.java @@ -0,0 +1,69 @@ +package ru.otus.spring.security; + +import org.springframework.security.access.expression.SecurityExpressionRoot; +import org.springframework.security.core.Authentication; + +public class AclMethodSecurityExpressionRoot extends SecurityExpressionRoot implements AclMethodSecurityExpressionOperations { + + private Object filterObject; + private Object returnObject; + private Object target; + + public AclMethodSecurityExpressionRoot(Authentication authentication) { + super(authentication); + } + + void setThis(Object target) { + this.target = target; + } + + @Override + public Object getFilterObject() { + return filterObject; + } + + @Override + public void setFilterObject(Object filterObject) { + this.filterObject = filterObject; + } + + @Override + public Object getReturnObject() { + return returnObject; + } + + @Override + public void setReturnObject(Object returnObject) { + this.returnObject = returnObject; + } + + @Override + public Object getThis() { + return this.target; + } + + @Override + public boolean isAdministrator(Object targetId, Class targetClass) { + + return isGranted(targetId, targetClass, admin); + } + + @Override + public boolean isAdministrator(Object target) { + + return hasPermission(target, admin); + } + + @Override + public boolean canRead(Object targetId, Class targetClass) { + + if(isAdministrator(targetId, targetClass)) return true; + + return isGranted(targetId, targetClass, read); + } + + boolean isGranted(Object targetId, Class targetClass, Object permission) { + + return hasPermission(targetId, targetClass.getCanonicalName(), permission); + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java new file mode 100644 index 00000000..0919dad9 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java @@ -0,0 +1,42 @@ +package ru.otus.spring.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import java.util.ArrayList; + +@EnableWebSecurity +public class SecurityConfiguration { + + @Bean + public SecurityFilterChain securityFilterChain( HttpSecurity http ) throws Exception { + http + .csrf().disable() + .authorizeHttpRequests( ( authorize ) -> authorize + .antMatchers( "/**", "/" ).permitAll() + ) + .httpBasic(); + return http.build(); + } + + @Bean + public InMemoryUserDetailsManager userDetailsService() { + var users = new ArrayList(); + users.add( User + .withDefaultPasswordEncoder().username("admin").password("password").roles("USER") + .build() ); + users.add( User + .withDefaultPasswordEncoder().username("user").password("password").roles("USER") + .build() ); + users.add( User + .withDefaultPasswordEncoder().username("someone").password("password").roles("EDITOR") + .build() ); + return new InMemoryUserDetailsManager( users ); + + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperService.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperService.java new file mode 100644 index 00000000..33783ff5 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperService.java @@ -0,0 +1,9 @@ +package ru.otus.spring.service; + +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.model.Permission; + +public interface AclServiceWrapperService { + + void createPermission(Object object, Permission permission); +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperServiceImpl.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperServiceImpl.java new file mode 100644 index 00000000..d5fdcd45 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperServiceImpl.java @@ -0,0 +1,34 @@ +package ru.otus.spring.service; + +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.domain.GrantedAuthoritySid; +import org.springframework.security.acls.domain.ObjectIdentityImpl; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +@Service +public class AclServiceWrapperServiceImpl implements AclServiceWrapperService { + + private final MutableAclService mutableAclService; + + public AclServiceWrapperServiceImpl(MutableAclService mutableAclService) { + this.mutableAclService = mutableAclService; + } + + @Override + public void createPermission(Object object, Permission permission) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + final Sid owner = new PrincipalSid(authentication); + ObjectIdentity oid = new ObjectIdentityImpl(object); + + final Sid admin = new GrantedAuthoritySid("ROLE_EDITOR"); + + MutableAcl acl = mutableAclService.createAcl(oid); + acl.insertAce(acl.getEntries().size(), permission, owner, true); + acl.insertAce(acl.getEntries().size(), permission, admin, true); + mutableAclService.updateAcl(acl); + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeService.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeService.java new file mode 100644 index 00000000..efb6a0b2 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeService.java @@ -0,0 +1,16 @@ +package ru.otus.spring.service; + +import ru.otus.spring.model.NoticeMessage; + +import java.util.List; + +public interface NoticeService { + + NoticeMessage create(NoticeMessage message); + + NoticeMessage get(Integer id); + + List getAll(); + + NoticeMessage update(NoticeMessage message); +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeServiceImpl.java b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeServiceImpl.java new file mode 100644 index 00000000..e5b43913 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeServiceImpl.java @@ -0,0 +1,60 @@ +package ru.otus.spring.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.domain.GrantedAuthoritySid; +import org.springframework.security.acls.domain.ObjectIdentityImpl; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.MutableAcl; +import org.springframework.security.acls.model.MutableAclService; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.otus.spring.model.NoticeMessage; +import ru.otus.spring.repository.NoticeMessageRepository; + +import java.util.List; + +@Service +public class NoticeServiceImpl implements NoticeService { + + private final AclServiceWrapperService aclServiceWrapperService; + + private final NoticeMessageRepository repository; + + public NoticeServiceImpl(AclServiceWrapperService aclServiceWrapperService, NoticeMessageRepository repository) { + this.aclServiceWrapperService = aclServiceWrapperService; + this.repository = repository; + } + + @Override + @Transactional + public NoticeMessage create(NoticeMessage message) { + NoticeMessage savedMessage = repository.save(message); + aclServiceWrapperService.createPermission(savedMessage, BasePermission.READ); + return savedMessage; + } + + @Override + @PostFilter("hasPermission(filterObject, 'READ')") + public List getAll() { + return repository.findAll(); + } + + @Override + @PreAuthorize("hasPermission(#message, 'WRITE')") + public NoticeMessage update(NoticeMessage message) { + return repository.save(message); + } + + @Override + @PreAuthorize("canRead(#id, T(ru.otus.spring.model.NoticeMessage))") + public NoticeMessage get(Integer id) { + return repository.findById(id).get(); + } +} diff --git a/2025-09/spring-23-ACL/ACL/src/main/resources/application.yml b/2025-09/spring-23-ACL/ACL/src/main/resources/application.yml new file mode 100644 index 00000000..02f0a284 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/resources/application.yml @@ -0,0 +1,13 @@ +spring: + h2: + console: + enabled: true + jpa: + hibernate: + ddl-auto: none + datasource: + url: jdbc:h2:mem:testdb + +springdoc: + packages-to-scan: ru.otus.spring.rest + paths-to-match: /** \ No newline at end of file diff --git a/2025-09/spring-23-ACL/ACL/src/main/resources/data.sql b/2025-09/spring-23-ACL/ACL/src/main/resources/data.sql new file mode 100644 index 00000000..9ae11203 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/resources/data.sql @@ -0,0 +1,28 @@ +INSERT INTO system_message(id,content) VALUES +(1,'First Level Message'), +(2,'Second Level Message'), +(3,'Third Level Message'); + + +INSERT INTO acl_sid (id, principal, sid) VALUES +(1, 1, 'admin'), +(2, 1, 'user'), +(3, 0, 'ROLE_EDITOR'); + +INSERT INTO acl_class (id, class) VALUES +(1, 'ru.otus.spring.model.NoticeMessage'); + +INSERT INTO acl_object_identity (id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) VALUES +(1, 1, 1, NULL, 3, 0), +(2, 1, 2, NULL, 3, 0), +(3, 1, 3, NULL, 3, 0); + +INSERT INTO acl_entry (id, acl_object_identity, ace_order, sid, mask, + granting, audit_success, audit_failure) VALUES +(1, 1, 1, 1, 1, 1, 1, 1), +(2, 1, 2, 1, 2, 1, 1, 1), +(3, 1, 3, 3, 1, 1, 1, 1), +(4, 2, 1, 2, 1, 1, 1, 1), +(5, 2, 2, 3, 1, 1, 1, 1), +(6, 3, 1, 3, 1, 1, 1, 1), +(7, 3, 2, 3, 2, 1, 1, 1); diff --git a/2025-09/spring-23-ACL/ACL/src/main/resources/postman_collection.json b/2025-09/spring-23-ACL/ACL/src/main/resources/postman_collection.json new file mode 100644 index 00000000..50f5f187 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/resources/postman_collection.json @@ -0,0 +1,88 @@ +{ + "info": { + "_postman_id": "00ffe5e9-877c-418a-b088-4409f34756e9", + "name": "New Collection", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "46752532", + "_collection_link": "https://uladzimirmaherau.postman.co/workspace/Uladzimir-Maherau's-Workspace~acb6d39e-e608-4ada-8982-80dabd38178f/collection/46752532-00ffe5e9-877c-418a-b088-4409f34756e9?action=share&source=collection_link&creator=46752532" + }, + "item": [ + { + "name": "http://localhost:8080/message", + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "admin", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": " {\r\n \"id\": 11,\r\n \"content\": \"11 Level Message\"\r\n }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/message", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "message" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/message", + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "admin", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/message", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "message" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/2025-09/spring-23-ACL/ACL/src/main/resources/schema.json b/2025-09/spring-23-ACL/ACL/src/main/resources/schema.json new file mode 100644 index 00000000..f3c13aa2 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/resources/schema.json @@ -0,0 +1,113 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "Generated server url" + } + ], + "paths": { + "/message": { + "get": { + "tags": [ + "notice-message-controller" + ], + "operationId": "getAll", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NoticeMessage" + } + } + } + } + } + } + }, + "put": { + "tags": [ + "notice-message-controller" + ], + "operationId": "getById", + "parameters": [ + { + "name": "message", + "in": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/NoticeMessage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/NoticeMessage" + } + } + } + } + } + } + }, + "/message/{id}": { + "get": { + "tags": [ + "notice-message-controller" + ], + "operationId": "getById_1", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/NoticeMessage" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "NoticeMessage": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "content": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/2025-09/spring-23-ACL/ACL/src/main/resources/schema.sql b/2025-09/spring-23-ACL/ACL/src/main/resources/schema.sql new file mode 100644 index 00000000..9f740482 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/resources/schema.sql @@ -0,0 +1,58 @@ +create table IF NOT EXISTS system_message (id integer not null, content varchar(255), primary key (id)); + +CREATE TABLE IF NOT EXISTS acl_sid ( + id bigint(20) NOT NULL AUTO_INCREMENT, + principal tinyint(1) NOT NULL, + sid varchar(100) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY unique_uk_1 (sid,principal) +); + +CREATE TABLE IF NOT EXISTS acl_class ( + id bigint(20) NOT NULL AUTO_INCREMENT, + class varchar(255) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY unique_uk_2 (class) +); + +CREATE TABLE IF NOT EXISTS acl_entry ( + id bigint(20) NOT NULL AUTO_INCREMENT, + acl_object_identity bigint(20) NOT NULL, + ace_order int(11) NOT NULL, + sid bigint(20) NOT NULL, + mask int(11) NOT NULL, + granting tinyint(1) NOT NULL, + audit_success tinyint(1) NOT NULL, + audit_failure tinyint(1) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY unique_uk_4 (acl_object_identity,ace_order) +); + +CREATE TABLE IF NOT EXISTS acl_object_identity ( + id bigint(20) NOT NULL AUTO_INCREMENT, + object_id_class bigint(20) NOT NULL, + object_id_identity bigint(20) NOT NULL, + parent_object bigint(20) DEFAULT NULL, + owner_sid bigint(20) DEFAULT NULL, + entries_inheriting tinyint(1) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY unique_uk_3 (object_id_class,object_id_identity) +); + +ALTER TABLE acl_entry +ADD FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id); + +ALTER TABLE acl_entry +ADD FOREIGN KEY (sid) REFERENCES acl_sid(id); + +-- +-- Constraints for table acl_object_identity +-- +ALTER TABLE acl_object_identity +ADD FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id); + +ALTER TABLE acl_object_identity +ADD FOREIGN KEY (object_id_class) REFERENCES acl_class (id); + +ALTER TABLE acl_object_identity +ADD FOREIGN KEY (owner_sid) REFERENCES acl_sid (id); \ No newline at end of file diff --git a/2025-09/spring-23-ACL/ACL/src/main/resources/templates/error.html b/2025-09/spring-23-ACL/ACL/src/main/resources/templates/error.html new file mode 100644 index 00000000..f28b51df --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/resources/templates/error.html @@ -0,0 +1,9 @@ + + + + + + +Вам доступ запрещён! + + diff --git a/2025-09/spring-23-ACL/ACL/src/main/resources/templates/index.html b/2025-09/spring-23-ACL/ACL/src/main/resources/templates/index.html new file mode 100644 index 00000000..79347f42 --- /dev/null +++ b/2025-09/spring-23-ACL/ACL/src/main/resources/templates/index.html @@ -0,0 +1,15 @@ + + + + + + +login +
+logout +
+h2-console +
+/swagger + + diff --git a/2025-09/spring-23-ACL/oauth/pom.xml b/2025-09/spring-23-ACL/oauth/pom.xml new file mode 100644 index 00000000..36300726 --- /dev/null +++ b/2025-09/spring-23-ACL/oauth/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + ru.otus + spring-framework-27-oauth + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.6.10 + + + + 2.6.11 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + + + + + org.springdoc + springdoc-openapi-ui + 1.6.9 + + + + org.webjars + jquery + 3.4.1 + + + org.webjars + bootstrap + 4.3.1 + + + org.webjars + webjars-locator-core + + + org.webjars + js-cookie + 2.1.0 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/GithubApplication.java b/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/GithubApplication.java new file mode 100644 index 00000000..7a69b021 --- /dev/null +++ b/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/GithubApplication.java @@ -0,0 +1,13 @@ +package ru.otus.spring.sso; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GithubApplication { + + public static void main(String[] args) { + SpringApplication.run( GithubApplication.class, args); + } + +} \ No newline at end of file diff --git a/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/IndexController.java b/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/IndexController.java new file mode 100644 index 00000000..a6e55cd2 --- /dev/null +++ b/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/IndexController.java @@ -0,0 +1,13 @@ +package ru.otus.spring.sso.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class IndexController { + + @GetMapping("/") + public String indexPage() { + return "index"; + } +} diff --git a/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/UserController.java b/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/UserController.java new file mode 100644 index 00000000..a99dc54d --- /dev/null +++ b/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/UserController.java @@ -0,0 +1,17 @@ +package ru.otus.spring.sso.controller; + +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.Map; + +@RestController +public class UserController { + @GetMapping("/user") + public Map user( @AuthenticationPrincipal OAuth2User principal) { + return Collections.singletonMap("name", principal.getAttribute("name")); + } +} diff --git a/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/security/SecurityConfig.java b/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/security/SecurityConfig.java new file mode 100644 index 00000000..b357a066 --- /dev/null +++ b/2025-09/spring-23-ACL/oauth/src/main/java/ru/otus/spring/sso/security/SecurityConfig.java @@ -0,0 +1,26 @@ +package ru.otus.spring.sso.security; + +import org.springframework.http.HttpStatus; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.authentication.HttpStatusEntryPoint; + +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure( HttpSecurity http ) throws Exception { + http + .authorizeRequests( a -> a + .antMatchers( "/", "/error", "/webjars/**" ).permitAll() + .anyRequest().authenticated() + ) + .exceptionHandling( e -> e + .authenticationEntryPoint( new HttpStatusEntryPoint( HttpStatus.UNAUTHORIZED ) ) + ) + .csrf().disable() + .logout( l -> l + .logoutSuccessUrl( "/" ).permitAll() + ) + + .oauth2Login(); + } +} diff --git a/2025-09/spring-23-ACL/oauth/src/main/resources/application.yml b/2025-09/spring-23-ACL/oauth/src/main/resources/application.yml new file mode 100644 index 00000000..c595efd2 --- /dev/null +++ b/2025-09/spring-23-ACL/oauth/src/main/resources/application.yml @@ -0,0 +1,13 @@ +spring: + security: + oauth2: + client: + registration: + github: + clientId: Ov23liJ3EIv6UfvJ4jPr + clientSecret: 0cb7763869aaa2151a399336c64cdef2618c6332 + +logging: + level: + root: error + org.springframework.security: DEBUG \ No newline at end of file diff --git a/2025-09/spring-23-ACL/oauth/src/main/resources/templates/index.html b/2025-09/spring-23-ACL/oauth/src/main/resources/templates/index.html new file mode 100644 index 00000000..09dae5ba --- /dev/null +++ b/2025-09/spring-23-ACL/oauth/src/main/resources/templates/index.html @@ -0,0 +1,18 @@ + + + + + + Demo + + + + + + + + +

Demo

+
+ + \ No newline at end of file