001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure.security.reactive;
018
019import java.util.EnumSet;
020import java.util.LinkedHashSet;
021import java.util.List;
022import java.util.Set;
023import java.util.stream.Collectors;
024import java.util.stream.Stream;
025
026import reactor.core.publisher.Mono;
027
028import org.springframework.boot.autoconfigure.security.StaticResourceLocation;
029import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
030import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
031import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
032import org.springframework.util.Assert;
033import org.springframework.web.server.ServerWebExchange;
034
035/**
036 * Used to create a {@link ServerWebExchangeMatcher} for static resources in commonly used
037 * locations. Returned by {@link PathRequest#toStaticResources()}.
038 *
039 * @author Madhura Bhave
040 * @since 2.0.0
041 * @see PathRequest
042 */
043public final class StaticResourceRequest {
044
045        static final StaticResourceRequest INSTANCE = new StaticResourceRequest();
046
047        private StaticResourceRequest() {
048        }
049
050        /**
051         * Returns a matcher that includes all commonly used {@link StaticResourceLocation
052         * Locations}. The
053         * {@link StaticResourceServerWebExchange#excluding(StaticResourceLocation, StaticResourceLocation...)
054         * excluding} method can be used to remove specific locations if required. For
055         * example: <pre class="code">
056         * PathRequest.toStaticResources().atCommonLocations().excluding(StaticResourceLocation.CSS)
057         * </pre>
058         * @return the configured {@link ServerWebExchangeMatcher}
059         */
060        public StaticResourceServerWebExchange atCommonLocations() {
061                return at(EnumSet.allOf(StaticResourceLocation.class));
062        }
063
064        /**
065         * Returns a matcher that includes the specified {@link StaticResourceLocation
066         * Locations}. For example: <pre class="code">
067         * PathRequest.toStaticResources().at(StaticResourceLocation.CSS, StaticResourceLocation.JAVA_SCRIPT)
068         * </pre>
069         * @param first the first location to include
070         * @param rest additional locations to include
071         * @return the configured {@link ServerWebExchangeMatcher}
072         */
073        public StaticResourceServerWebExchange at(StaticResourceLocation first,
074                        StaticResourceLocation... rest) {
075                return at(EnumSet.of(first, rest));
076        }
077
078        /**
079         * Returns a matcher that includes the specified {@link StaticResourceLocation
080         * Locations}. For example: <pre class="code">
081         * PathRequest.toStaticResources().at(locations)
082         * </pre>
083         * @param locations the locations to include
084         * @return the configured {@link ServerWebExchangeMatcher}
085         */
086        public StaticResourceServerWebExchange at(Set<StaticResourceLocation> locations) {
087                Assert.notNull(locations, "Locations must not be null");
088                return new StaticResourceServerWebExchange(new LinkedHashSet<>(locations));
089        }
090
091        /**
092         * The server web exchange matcher used to match against resource
093         * {@link StaticResourceLocation locations}.
094         */
095        public static final class StaticResourceServerWebExchange
096                        implements ServerWebExchangeMatcher {
097
098                private final Set<StaticResourceLocation> locations;
099
100                private StaticResourceServerWebExchange(Set<StaticResourceLocation> locations) {
101                        this.locations = locations;
102                }
103
104                /**
105                 * Return a new {@link StaticResourceServerWebExchange} based on this one but
106                 * excluding the specified locations.
107                 * @param first the first location to exclude
108                 * @param rest additional locations to exclude
109                 * @return a new {@link StaticResourceServerWebExchange}
110                 */
111                public StaticResourceServerWebExchange excluding(StaticResourceLocation first,
112                                StaticResourceLocation... rest) {
113                        return excluding(EnumSet.of(first, rest));
114                }
115
116                /**
117                 * Return a new {@link StaticResourceServerWebExchange} based on this one but
118                 * excluding the specified locations.
119                 * @param locations the locations to exclude
120                 * @return a new {@link StaticResourceServerWebExchange}
121                 */
122                public StaticResourceServerWebExchange excluding(
123                                Set<StaticResourceLocation> locations) {
124                        Assert.notNull(locations, "Locations must not be null");
125                        Set<StaticResourceLocation> subset = new LinkedHashSet<>(this.locations);
126                        subset.removeAll(locations);
127                        return new StaticResourceServerWebExchange(subset);
128                }
129
130                private List<ServerWebExchangeMatcher> getDelegateMatchers() {
131                        return getPatterns().map(PathPatternParserServerWebExchangeMatcher::new)
132                                        .collect(Collectors.toList());
133                }
134
135                private Stream<String> getPatterns() {
136                        return this.locations.stream().flatMap(StaticResourceLocation::getPatterns);
137                }
138
139                @Override
140                public Mono<MatchResult> matches(ServerWebExchange exchange) {
141                        OrServerWebExchangeMatcher matcher = new OrServerWebExchangeMatcher(
142                                        getDelegateMatchers());
143                        return matcher.matches(exchange);
144                }
145
146        }
147
148}