11package run .halo .app .theme .dialect ;
22
33import static org .springframework .security .core .authority .AuthorityUtils .createAuthorityList ;
4+ import static org .thymeleaf .extras .springsecurity6 .dialect .processor .AuthorizeAttrProcessor .ATTR_NAME ;
5+ import static org .thymeleaf .extras .springsecurity6 .dialect .processor .AuthorizeAttrProcessor .ATTR_PRECEDENCE ;
46import static run .halo .app .infra .AnonymousUserConst .PRINCIPAL ;
57import static run .halo .app .infra .AnonymousUserConst .Role ;
68
9+ import java .util .LinkedHashSet ;
10+ import java .util .Optional ;
11+ import java .util .Set ;
712import java .util .function .Function ;
813import org .springframework .beans .factory .InitializingBean ;
14+ import org .springframework .beans .factory .ObjectProvider ;
15+ import org .springframework .security .access .expression .ExpressionUtils ;
16+ import org .springframework .security .access .expression .method .MethodSecurityExpressionHandler ;
917import org .springframework .security .authentication .AnonymousAuthenticationToken ;
18+ import org .springframework .security .core .Authentication ;
1019import org .springframework .security .core .context .SecurityContextImpl ;
20+ import org .springframework .security .util .MethodInvocationUtils ;
1121import org .springframework .security .web .server .context .ServerSecurityContextRepository ;
1222import org .springframework .web .server .ServerWebExchange ;
23+ import org .thymeleaf .context .ITemplateContext ;
24+ import org .thymeleaf .engine .AttributeName ;
25+ import org .thymeleaf .extras .springsecurity6 .auth .AuthUtils ;
1326import org .thymeleaf .extras .springsecurity6 .dialect .SpringSecurityDialect ;
1427import org .thymeleaf .extras .springsecurity6 .util .SpringSecurityContextUtils ;
28+ import org .thymeleaf .extras .springsecurity6 .util .SpringVersionSpecificUtils ;
1529import org .thymeleaf .extras .springsecurity6 .util .SpringVersionUtils ;
30+ import org .thymeleaf .model .IProcessableElementTag ;
31+ import org .thymeleaf .processor .IProcessor ;
32+ import org .thymeleaf .standard .processor .AbstractStandardConditionalVisibilityTagProcessor ;
33+ import org .thymeleaf .templatemode .TemplateMode ;
1634import run .halo .app .security .authorization .AuthorityUtils ;
1735
1836/**
@@ -28,8 +46,12 @@ public class HaloSpringSecurityDialect extends SpringSecurityDialect implements
2846
2947 private final ServerSecurityContextRepository securityContextRepository ;
3048
31- public HaloSpringSecurityDialect (ServerSecurityContextRepository securityContextRepository ) {
49+ private final ObjectProvider <MethodSecurityExpressionHandler > expressionHandler ;
50+
51+ public HaloSpringSecurityDialect (ServerSecurityContextRepository securityContextRepository ,
52+ ObjectProvider <MethodSecurityExpressionHandler > expressionHandler ) {
3253 this .securityContextRepository = securityContextRepository ;
54+ this .expressionHandler = expressionHandler ;
3355 }
3456
3557 @ Override
@@ -53,4 +75,81 @@ public void afterPropertiesSet() {
5375 // Just overwrite the value of the attribute
5476 getExecutionAttributes ().put (SECURITY_CONTEXT_EXECUTION_ATTRIBUTE_NAME , secCtxInitializer );
5577 }
78+
79+ @ Override
80+ public Set <IProcessor > getProcessors (String dialectPrefix ) {
81+ LinkedHashSet <IProcessor > processors = new LinkedHashSet <>();
82+ processors .add (
83+ new HaloAuthorizeAttrProcessor (TemplateMode .HTML , dialectPrefix , ATTR_NAME )
84+ );
85+ processors .addAll (super .getProcessors (dialectPrefix ));
86+ return processors ;
87+ }
88+
89+ public class HaloAuthorizeAttrProcessor
90+ extends AbstractStandardConditionalVisibilityTagProcessor {
91+
92+ protected HaloAuthorizeAttrProcessor (TemplateMode templateMode , String dialectPrefix ,
93+ String attrName ) {
94+ super (templateMode , dialectPrefix , attrName , ATTR_PRECEDENCE - 10 );
95+ }
96+
97+ @ Override
98+ protected boolean isVisible (ITemplateContext context , IProcessableElementTag tag ,
99+ AttributeName attributeName , String attributeValue ) {
100+
101+ final String attrValue = (attributeValue == null ? null : attributeValue .trim ());
102+
103+ if (attrValue == null || attrValue .isEmpty ()) {
104+ return false ;
105+ }
106+
107+ final Authentication authentication = AuthUtils .getAuthenticationObject (context );
108+
109+ if (authentication == null ) {
110+ return false ;
111+ }
112+
113+ // resolve expr
114+ var expr = Optional .of (attributeValue )
115+ .filter (v -> v .startsWith ("${" ) && v .endsWith ("}" ))
116+ .map (v -> v .substring (2 , v .length () - 1 ))
117+ .orElse (attributeValue );
118+
119+ var expressionHandler = HaloSpringSecurityDialect .this .expressionHandler .getIfUnique ();
120+ if (expressionHandler == null ) {
121+ // no expression handler found
122+ return false ;
123+ }
124+
125+ var expression = expressionHandler .getExpressionParser ().parseExpression (expr );
126+
127+ var methodInvocation = MethodInvocationUtils .createFromClass (this ,
128+ HaloAuthorizeAttrProcessor .class ,
129+ "dummyAuthorize" ,
130+ new Class [] {Authentication .class },
131+ new Object [] {authentication }
132+ );
133+ var evaluationContext =
134+ expressionHandler .createEvaluationContext (authentication , methodInvocation );
135+
136+ var expressionObjects = context .getExpressionObjects ();
137+ var wrappedEvolutionContext = SpringVersionSpecificUtils .wrapEvaluationContext (
138+ evaluationContext , expressionObjects
139+ );
140+
141+ return ExpressionUtils .evaluateAsBoolean (expression , wrappedEvolutionContext );
142+ }
143+
144+ /**
145+ * This method is only used to create a method invocation for the expression parser.
146+ *
147+ * @param authentication authentication object
148+ * @return result of authorization expression evaluation
149+ */
150+ public Boolean dummyAuthorize (Authentication authentication ) {
151+ throw new UnsupportedOperationException ("Should not be called" );
152+ }
153+
154+ }
56155}
0 commit comments