Skip to content

Commit f5beae8

Browse files
committed
[ENG-8791] Inserting CasProperties into Spring Dispatcher and Improve header and left pane UI/UX (#92)
* Overlay spring-boot and sprint-webmvc classes * DispatcherServlet as of spring-webmvc 5.2.6.RELEASE * WebMvcEndpointChildContextConfiguration as of spring-boot 2.2.8.RELEASE * DispatcherServletAutoConfiguration as of spring-boot 2.2.8.RELEASE * Override DispatcherServlet config and impl to support OsfUrlProperties * Update left pane to use use OsfUrlProperties from customized Dispatcher * Improve left pane and header for osf layout * Sync layout, left pane and header between between default and osf * Fix sign-up button and remove dead code
1 parent 23e6541 commit f5beae8

File tree

12 files changed

+2028
-82
lines changed

12 files changed

+2028
-82
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.web.servlet;
18+
19+
import org.apereo.cas.configuration.CasConfigurationProperties;
20+
21+
import org.springframework.beans.factory.ListableBeanFactory;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
24+
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
30+
import org.springframework.boot.autoconfigure.web.ServerProperties;
31+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
32+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
33+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
34+
import org.springframework.boot.web.server.ErrorPage;
35+
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
36+
import org.springframework.boot.web.servlet.error.ErrorAttributes;
37+
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
38+
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
39+
import org.springframework.context.annotation.Bean;
40+
import org.springframework.core.Ordered;
41+
import org.springframework.web.context.request.RequestContextListener;
42+
import org.springframework.web.filter.RequestContextFilter;
43+
import org.springframework.web.servlet.DispatcherServlet;
44+
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
45+
46+
/**
47+
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for Spring MVC
48+
* infrastructure when a separate management context with a web server running on a
49+
* different port is required.
50+
*
51+
* <p>OSF CAS Customization: introduce a new field {@link CasConfigurationProperties casProperties} to configuration
52+
* class {@link WebMvcEndpointChildContextConfiguration}; this allows the configuration to initiate osf-cas-customized
53+
* {@link DispatcherServlet dispatcherServlet} with {@link io.cos.cas.osf.configuration.model.OsfUrlProperties}.</p>
54+
*
55+
* @author Stephane Nicoll
56+
* @author Andy Wilkinson
57+
* @author Phillip Webb
58+
*
59+
* @author Longze Chen
60+
* @since osf-cas 25.1.0
61+
*/
62+
@ManagementContextConfiguration(ManagementContextType.CHILD)
63+
@ConditionalOnWebApplication(type = Type.SERVLET)
64+
@ConditionalOnClass(DispatcherServlet.class)
65+
@EnableWebMvc
66+
@EnableConfigurationProperties(CasConfigurationProperties.class)
67+
class WebMvcEndpointChildContextConfiguration {
68+
69+
// OSF CAS Customization: a new private field casProperties, in which osfUrlProperties is embedded
70+
@Autowired
71+
private CasConfigurationProperties casProperties;
72+
73+
/*
74+
* The error controller is present but not mapped as an endpoint in this context
75+
* because of the DispatcherServlet having had its HandlerMapping explicitly disabled.
76+
* So we expose the same feature but only for machine endpoints.
77+
*/
78+
@Bean
79+
@ConditionalOnBean(ErrorAttributes.class)
80+
ManagementErrorEndpoint errorEndpoint(ErrorAttributes errorAttributes) {
81+
return new ManagementErrorEndpoint(errorAttributes);
82+
}
83+
84+
@Bean
85+
@ConditionalOnBean(ErrorAttributes.class)
86+
ManagementErrorPageCustomizer managementErrorPageCustomizer(ServerProperties serverProperties) {
87+
return new ManagementErrorPageCustomizer(serverProperties);
88+
}
89+
90+
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
91+
DispatcherServlet dispatcherServlet() {
92+
DispatcherServlet dispatcherServlet = new DispatcherServlet();
93+
// OSF CAS Customization: set OsfUrlProperties for dispatcherServlet
94+
dispatcherServlet.setOsfUrlProperties(casProperties.getAuthn().getOsfUrl());
95+
// Ensure the parent configuration does not leak down to us
96+
dispatcherServlet.setDetectAllHandlerAdapters(false);
97+
dispatcherServlet.setDetectAllHandlerExceptionResolvers(false);
98+
dispatcherServlet.setDetectAllHandlerMappings(false);
99+
dispatcherServlet.setDetectAllViewResolvers(false);
100+
return dispatcherServlet;
101+
}
102+
103+
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
104+
DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
105+
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
106+
}
107+
108+
@Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME)
109+
CompositeHandlerMapping compositeHandlerMapping() {
110+
return new CompositeHandlerMapping();
111+
}
112+
113+
@Bean(name = DispatcherServlet.HANDLER_ADAPTER_BEAN_NAME)
114+
CompositeHandlerAdapter compositeHandlerAdapter(ListableBeanFactory beanFactory) {
115+
return new CompositeHandlerAdapter(beanFactory);
116+
}
117+
118+
@Bean(name = DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME)
119+
CompositeHandlerExceptionResolver compositeHandlerExceptionResolver() {
120+
return new CompositeHandlerExceptionResolver();
121+
}
122+
123+
@Bean
124+
@ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
125+
RequestContextFilter requestContextFilter() {
126+
return new OrderedRequestContextFilter();
127+
}
128+
129+
/**
130+
* {@link WebServerFactoryCustomizer} to add an {@link ErrorPage} so that the
131+
* {@link ManagementErrorEndpoint} can be used.
132+
*/
133+
private static class ManagementErrorPageCustomizer
134+
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
135+
136+
private final ServerProperties properties;
137+
138+
ManagementErrorPageCustomizer(ServerProperties properties) {
139+
this.properties = properties;
140+
}
141+
142+
@Override
143+
public void customize(ConfigurableServletWebServerFactory factory) {
144+
factory.addErrorPages(new ErrorPage(this.properties.getError().getPath()));
145+
}
146+
147+
@Override
148+
public int getOrder() {
149+
return 0;
150+
}
151+
152+
}
153+
154+
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web.servlet;
18+
19+
import java.util.Arrays;
20+
import java.util.List;
21+
22+
import javax.servlet.MultipartConfigElement;
23+
import javax.servlet.ServletRegistration;
24+
25+
import org.apereo.cas.configuration.CasConfigurationProperties;
26+
27+
import org.springframework.beans.factory.ObjectProvider;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
30+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
31+
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
32+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
33+
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
34+
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
35+
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
36+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
37+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
38+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
39+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
40+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
41+
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
42+
import org.springframework.boot.autoconfigure.http.HttpProperties;
43+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
44+
import org.springframework.boot.web.servlet.ServletRegistrationBean;
45+
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
46+
import org.springframework.context.annotation.Bean;
47+
import org.springframework.context.annotation.ConditionContext;
48+
import org.springframework.context.annotation.Conditional;
49+
import org.springframework.context.annotation.Configuration;
50+
import org.springframework.context.annotation.Import;
51+
import org.springframework.core.Ordered;
52+
import org.springframework.core.annotation.Order;
53+
import org.springframework.core.type.AnnotatedTypeMetadata;
54+
import org.springframework.web.multipart.MultipartResolver;
55+
import org.springframework.web.servlet.DispatcherServlet;
56+
57+
/**
58+
* {@link EnableAutoConfiguration Auto-configuration} for the Spring
59+
* {@link DispatcherServlet}. Should work for a standalone application where an embedded
60+
* web server is already present and also for a deployable application using
61+
* {@link SpringBootServletInitializer}.
62+
*
63+
* <p>OSF CAS Customization: introduce a new field {@link CasConfigurationProperties casProperties} to configuration
64+
* class {@link DispatcherServletAutoConfiguration}; this allows the configuration to initiate osf-cas-customized
65+
* {@link DispatcherServlet dispatcherServlet} with {@link io.cos.cas.osf.configuration.model.OsfUrlProperties}.</p>
66+
*
67+
* @author Phillip Webb
68+
* @author Dave Syer
69+
* @author Stephane Nicoll
70+
* @author Brian Clozel
71+
* @since 2.0.0
72+
*
73+
* @author Longze Chen
74+
* @since osf-cas 25.1.0
75+
*/
76+
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
77+
@Configuration(proxyBeanMethods = false)
78+
@ConditionalOnWebApplication(type = Type.SERVLET)
79+
@ConditionalOnClass(DispatcherServlet.class)
80+
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
81+
@EnableConfigurationProperties(CasConfigurationProperties.class)
82+
public class DispatcherServletAutoConfiguration {
83+
84+
// OSF CAS Customization: a new private field casProperties, in which osfUrlProperties is embedded
85+
@Autowired
86+
private CasConfigurationProperties casProperties;
87+
88+
/*
89+
* The bean name for a DispatcherServlet that will be mapped to the root URL "/"
90+
*/
91+
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
92+
93+
/*
94+
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
95+
*/
96+
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
97+
98+
@Configuration(proxyBeanMethods = false)
99+
@Conditional(DefaultDispatcherServletCondition.class)
100+
@ConditionalOnClass(ServletRegistration.class)
101+
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class, CasConfigurationProperties.class})
102+
protected static class DispatcherServletConfiguration {
103+
104+
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
105+
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties, CasConfigurationProperties casProperties) {
106+
DispatcherServlet dispatcherServlet = new DispatcherServlet();
107+
// OSF CAS Customization: set OsfUrlProperties for dispatcherServlet
108+
dispatcherServlet.setOsfUrlProperties(casProperties.getAuthn().getOsfUrl());
109+
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
110+
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
111+
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
112+
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
113+
dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
114+
return dispatcherServlet;
115+
}
116+
117+
@Bean
118+
@ConditionalOnBean(MultipartResolver.class)
119+
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
120+
public MultipartResolver multipartResolver(MultipartResolver resolver) {
121+
// Detect if the user has created a MultipartResolver but named it incorrectly
122+
return resolver;
123+
}
124+
125+
}
126+
127+
@Configuration(proxyBeanMethods = false)
128+
@Conditional(DispatcherServletRegistrationCondition.class)
129+
@ConditionalOnClass(ServletRegistration.class)
130+
@EnableConfigurationProperties(WebMvcProperties.class)
131+
@Import(DispatcherServletConfiguration.class)
132+
protected static class DispatcherServletRegistrationConfiguration {
133+
134+
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
135+
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
136+
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
137+
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
138+
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
139+
webMvcProperties.getServlet().getPath());
140+
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
141+
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
142+
multipartConfig.ifAvailable(registration::setMultipartConfig);
143+
return registration;
144+
}
145+
146+
}
147+
148+
@Order(Ordered.LOWEST_PRECEDENCE - 10)
149+
private static class DefaultDispatcherServletCondition extends SpringBootCondition {
150+
151+
@Override
152+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
153+
ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
154+
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
155+
List<String> dispatchServletBeans = Arrays
156+
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
157+
if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
158+
return ConditionOutcome
159+
.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
160+
}
161+
if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
162+
return ConditionOutcome.noMatch(
163+
message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
164+
}
165+
if (dispatchServletBeans.isEmpty()) {
166+
return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
167+
}
168+
return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
169+
.items(Style.QUOTE, dispatchServletBeans)
170+
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
171+
}
172+
173+
}
174+
175+
@Order(Ordered.LOWEST_PRECEDENCE - 10)
176+
private static class DispatcherServletRegistrationCondition extends SpringBootCondition {
177+
178+
@Override
179+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
180+
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
181+
ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);
182+
if (!outcome.isMatch()) {
183+
return outcome;
184+
}
185+
return checkServletRegistration(beanFactory);
186+
}
187+
188+
private ConditionOutcome checkDefaultDispatcherName(ConfigurableListableBeanFactory beanFactory) {
189+
List<String> servlets = Arrays
190+
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
191+
boolean containsDispatcherBean = beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
192+
if (containsDispatcherBean && !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
193+
return ConditionOutcome.noMatch(
194+
startMessage().found("non dispatcher servlet").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
195+
}
196+
return ConditionOutcome.match();
197+
}
198+
199+
private ConditionOutcome checkServletRegistration(ConfigurableListableBeanFactory beanFactory) {
200+
ConditionMessage.Builder message = startMessage();
201+
List<String> registrations = Arrays
202+
.asList(beanFactory.getBeanNamesForType(ServletRegistrationBean.class, false, false));
203+
boolean containsDispatcherRegistrationBean = beanFactory
204+
.containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
205+
if (registrations.isEmpty()) {
206+
if (containsDispatcherRegistrationBean) {
207+
return ConditionOutcome.noMatch(message.found("non servlet registration bean")
208+
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
209+
}
210+
return ConditionOutcome.match(message.didNotFind("servlet registration bean").atAll());
211+
}
212+
if (registrations.contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
213+
return ConditionOutcome.noMatch(message.found("servlet registration bean")
214+
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
215+
}
216+
if (containsDispatcherRegistrationBean) {
217+
return ConditionOutcome.noMatch(message.found("non servlet registration bean")
218+
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
219+
}
220+
return ConditionOutcome.match(message.found("servlet registration beans").items(Style.QUOTE, registrations)
221+
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
222+
}
223+
224+
private ConditionMessage.Builder startMessage() {
225+
return ConditionMessage.forCondition("DispatcherServlet Registration");
226+
}
227+
228+
}
229+
230+
}

0 commit comments

Comments
 (0)