diff --git a/2024-07/spring-26-ACL/ACL/pom.xml b/2024-07/spring-26-ACL/ACL/pom.xml
new file mode 100644
index 00000000..2ba139f5
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/Main.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..baca49d5
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java
new file mode 100644
index 00000000..e0363d94
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java
new file mode 100644
index 00000000..501d91a2
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java
new file mode 100644
index 00000000..01bb4154
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclConfig.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclConfig.java
new file mode 100644
index 00000000..d261e2a7
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java
new file mode 100644
index 00000000..5fb8a778
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionHandler.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionHandler.java
new file mode 100644
index 00000000..ef30e5f3
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionOperations.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionOperations.java
new file mode 100644
index 00000000..f7885ab0
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionRoot.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityExpressionRoot.java
new file mode 100644
index 00000000..73cf1068
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
new file mode 100644
index 00000000..0919dad9
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperService.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperService.java
new file mode 100644
index 00000000..33783ff5
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperServiceImpl.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/AclServiceWrapperServiceImpl.java
new file mode 100644
index 00000000..b5526465
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeService.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeService.java
new file mode 100644
index 00000000..efb6a0b2
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeServiceImpl.java b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeServiceImpl.java
new file mode 100644
index 00000000..6dc728c1
--- /dev/null
+++ b/2024-07/spring-26-ACL/ACL/src/main/java/ru/otus/spring/service/NoticeServiceImpl.java
@@ -0,0 +1,58 @@
+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 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
+ 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/2024-07/spring-26-ACL/ACL/src/main/resources/application.yml b/2024-07/spring-26-ACL/ACL/src/main/resources/application.yml
new file mode 100644
index 00000000..02f0a284
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/resources/data.sql b/2024-07/spring-26-ACL/ACL/src/main/resources/data.sql
new file mode 100644
index 00000000..9ae11203
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/resources/schema.json b/2024-07/spring-26-ACL/ACL/src/main/resources/schema.json
new file mode 100644
index 00000000..f3c13aa2
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/resources/schema.sql b/2024-07/spring-26-ACL/ACL/src/main/resources/schema.sql
new file mode 100644
index 00000000..9f740482
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/ACL/src/main/resources/templates/error.html b/2024-07/spring-26-ACL/ACL/src/main/resources/templates/error.html
new file mode 100644
index 00000000..f28b51df
--- /dev/null
+++ b/2024-07/spring-26-ACL/ACL/src/main/resources/templates/error.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Вам доступ запрещён!
+
+
diff --git a/2024-07/spring-26-ACL/ACL/src/main/resources/templates/index.html b/2024-07/spring-26-ACL/ACL/src/main/resources/templates/index.html
new file mode 100644
index 00000000..79347f42
--- /dev/null
+++ b/2024-07/spring-26-ACL/ACL/src/main/resources/templates/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+login
+
+logout
+
+h2-console
+
+/swagger
+
+
diff --git a/2024-07/spring-26-ACL/oauth/pom.xml b/2024-07/spring-26-ACL/oauth/pom.xml
new file mode 100644
index 00000000..36300726
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/oauth/src/main/java/ru/otus/spring/sso/GithubApplication.java b/2024-07/spring-26-ACL/oauth/src/main/java/ru/otus/spring/sso/GithubApplication.java
new file mode 100644
index 00000000..7a69b021
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/IndexController.java b/2024-07/spring-26-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/IndexController.java
new file mode 100644
index 00000000..a6e55cd2
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/UserController.java b/2024-07/spring-26-ACL/oauth/src/main/java/ru/otus/spring/sso/controller/UserController.java
new file mode 100644
index 00000000..a99dc54d
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/oauth/src/main/java/ru/otus/spring/sso/security/SecurityConfig.java b/2024-07/spring-26-ACL/oauth/src/main/java/ru/otus/spring/sso/security/SecurityConfig.java
new file mode 100644
index 00000000..b357a066
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/oauth/src/main/resources/application.yml b/2024-07/spring-26-ACL/oauth/src/main/resources/application.yml
new file mode 100644
index 00000000..c595efd2
--- /dev/null
+++ b/2024-07/spring-26-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/2024-07/spring-26-ACL/oauth/src/main/resources/templates/index.html b/2024-07/spring-26-ACL/oauth/src/main/resources/templates/index.html
new file mode 100644
index 00000000..09dae5ba
--- /dev/null
+++ b/2024-07/spring-26-ACL/oauth/src/main/resources/templates/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Demo
+
+
+
+
+
+
+
+
+Demo
+
+
+
\ No newline at end of file