1 /*
2  *
3  *  * Copyright 2019-2020 the original author or authors.
4  *  *
5  *  * Licensed under the Apache License, Version 2.0 (the "License");
6  *  * you may not use this file except in compliance with the License.
7  *  * You may obtain a copy of the License at
8  *  *
9  *  *      https://www.apache.org/licenses/LICENSE-2.0
10  *  *
11  *  * Unless required by applicable law or agreed to in writing, software
12  *  * distributed under the License is distributed on an "AS IS" BASIS,
13  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  * See the License for the specific language governing permissions and
15  *  * limitations under the License.
16  *
17  */

18
19 package org.springdoc.core;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
28
29 import io.swagger.v3.core.util.AnnotationsUtils;
30 import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
31 import io.swagger.v3.oas.annotations.security.OAuthScope;
32 import io.swagger.v3.oas.models.Operation;
33 import io.swagger.v3.oas.models.security.OAuthFlow;
34 import io.swagger.v3.oas.models.security.OAuthFlows;
35 import io.swagger.v3.oas.models.security.Scopes;
36 import io.swagger.v3.oas.models.security.SecurityRequirement;
37 import io.swagger.v3.oas.models.security.SecurityScheme;
38 import org.apache.commons.lang3.StringUtils;
39
40 import org.springframework.core.annotation.AnnotatedElementUtils;
41 import org.springframework.util.CollectionUtils;
42 import org.springframework.web.method.HandlerMethod;
43
44 class SecurityParser {
45
46     private final PropertyResolverUtils propertyResolverUtils;
47
48     public SecurityParser(PropertyResolverUtils propertyResolverUtils) {
49         this.propertyResolverUtils = propertyResolverUtils;
50     }
51
52     private static boolean isEmpty(io.swagger.v3.oas.annotations.security.OAuthFlows oAuthFlows) {
53         boolean result;
54         if (oAuthFlows == null)
55             result = true;
56         else if (!isEmpty(oAuthFlows.implicit()) || !isEmpty(oAuthFlows.authorizationCode()) || !isEmpty(oAuthFlows.clientCredentials()) || !isEmpty(oAuthFlows.password()))
57             result = false;
58         else result = oAuthFlows.extensions().length <= 0;
59         return result;
60     }
61
62     private static boolean isEmpty(io.swagger.v3.oas.annotations.security.OAuthFlow oAuthFlow) {
63         boolean result;
64         if (oAuthFlow == null)
65             result = true;
66         else if (!StringUtils.isBlank(oAuthFlow.authorizationUrl()) || !StringUtils.isBlank(oAuthFlow.refreshUrl()) || !StringUtils.isBlank(oAuthFlow.tokenUrl()) || !isEmpty(oAuthFlow.scopes()))
67             result = false;
68         else result = oAuthFlow.extensions().length <= 0;
69         return result;
70     }
71
72     private static boolean isEmpty(OAuthScope[] scopes) {
73         boolean result = false;
74         if (scopes == null || scopes.length == 0)
75             result = true;
76         return result;
77     }
78
79     public io.swagger.v3.oas.annotations.security.SecurityRequirement[] getSecurityRequirements(
80             HandlerMethod method) {
81         // class SecurityRequirements
82         io.swagger.v3.oas.annotations.security.SecurityRequirements classSecurity = AnnotatedElementUtils.findMergedAnnotation(method.getBeanType(), io.swagger.v3.oas.annotations.security.SecurityRequirements.class);
83         // method SecurityRequirements
84         io.swagger.v3.oas.annotations.security.SecurityRequirements methodSecurity = AnnotatedElementUtils.findMergedAnnotation(method.getMethod(), io.swagger.v3.oas.annotations.security.SecurityRequirements.class);
85
86         Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> allSecurityTags = null;
87
88         if (classSecurity != null)
89             allSecurityTags = new HashSet<>(Arrays.asList(classSecurity.value()));
90         if (methodSecurity != null)
91             allSecurityTags = addSecurityRequirements(allSecurityTags, new HashSet<>(Arrays.asList(methodSecurity.value())));
92
93         if (CollectionUtils.isEmpty(allSecurityTags)) {
94             // class SecurityRequirement
95             Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> securityRequirementsClassList = AnnotatedElementUtils.findMergedRepeatableAnnotations(
96                     method.getBeanType(),
97                     io.swagger.v3.oas.annotations.security.SecurityRequirement.class);
98             // method SecurityRequirement
99             Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> securityRequirementsMethodList = AnnotatedElementUtils.findMergedRepeatableAnnotations(method.getMethod(),
100                     io.swagger.v3.oas.annotations.security.SecurityRequirement.class);
101             if (!CollectionUtils.isEmpty(securityRequirementsClassList))
102                 allSecurityTags = addSecurityRequirements(allSecurityTags, securityRequirementsClassList);
103             if (!CollectionUtils.isEmpty(securityRequirementsMethodList))
104                 allSecurityTags = addSecurityRequirements(allSecurityTags, securityRequirementsMethodList);
105         }
106
107         return (allSecurityTags != null) ? allSecurityTags.toArray(new io.swagger.v3.oas.annotations.security.SecurityRequirement[0]) : null;
108     }
109
110     private Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> addSecurityRequirements(Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> allSecurityTags, Set<io.swagger.v3.oas.annotations.security.SecurityRequirement> securityRequirementsClassList) {
111         if (allSecurityTags == null)
112             allSecurityTags = new HashSet<>();
113         allSecurityTags.addAll(securityRequirementsClassList);
114         return allSecurityTags;
115     }
116
117     public Optional<List<SecurityRequirement>> getSecurityRequirements(
118             io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirementsApi) {
119         if (securityRequirementsApi == null || securityRequirementsApi.length == 0)
120             return Optional.empty();
121         List<SecurityRequirement> securityRequirements = new ArrayList<>();
122         for (io.swagger.v3.oas.annotations.security.SecurityRequirement securityRequirementApi : securityRequirementsApi) {
123             if (StringUtils.isBlank(securityRequirementApi.name()))
124                 continue;
125             SecurityRequirement securityRequirement = new SecurityRequirement();
126             if (securityRequirementApi.scopes().length > 0)
127                 securityRequirement.addList(securityRequirementApi.name(), Arrays.asList(securityRequirementApi.scopes()));
128             else
129                 securityRequirement.addList(securityRequirementApi.name());
130             securityRequirements.add(securityRequirement);
131         }
132         if (securityRequirements.isEmpty())
133             return Optional.empty();
134         return Optional.of(securityRequirements);
135     }
136
137     public Optional<SecuritySchemePair> getSecurityScheme(
138             io.swagger.v3.oas.annotations.security.SecurityScheme securityScheme) {
139         if (securityScheme == null)
140             return Optional.empty();
141         String key = null;
142         SecurityScheme securitySchemeObject = new SecurityScheme();
143
144         if (StringUtils.isNotBlank(securityScheme.in().toString()))
145             securitySchemeObject.setIn(getIn(securityScheme.in().toString()));
146
147         if (StringUtils.isNotBlank(securityScheme.type().toString()))
148             securitySchemeObject.setType(getType(securityScheme.type().toString()));
149
150         if (StringUtils.isNotBlank(securityScheme.openIdConnectUrl()))
151             securitySchemeObject.setOpenIdConnectUrl(propertyResolverUtils.resolve(securityScheme.openIdConnectUrl()));
152
153         if (StringUtils.isNotBlank(securityScheme.scheme()))
154             securitySchemeObject.setScheme(securityScheme.scheme());
155
156         if (StringUtils.isNotBlank(securityScheme.bearerFormat()))
157             securitySchemeObject.setBearerFormat(securityScheme.bearerFormat());
158
159         if (StringUtils.isNotBlank(securityScheme.description()))
160             securitySchemeObject.setDescription(securityScheme.description());
161
162         if (StringUtils.isNotBlank(securityScheme.ref()))
163             securitySchemeObject.set$ref(securityScheme.ref());
164
165         if (StringUtils.isNotBlank(securityScheme.name())) {
166             key = securityScheme.name();
167             if (SecuritySchemeType.APIKEY.toString().equals(securitySchemeObject.getType().toString()))
168                 securitySchemeObject.setName(securityScheme.name());
169         }
170         if (StringUtils.isNotBlank(securityScheme.paramName()))
171             securitySchemeObject.setName(securityScheme.paramName());
172
173         if (securityScheme.extensions().length > 0) {
174             Map<String, Object> extensions = AnnotationsUtils.getExtensions(securityScheme.extensions());
175             extensions.forEach(securitySchemeObject::addExtension);
176         }
177
178         getOAuthFlows(securityScheme.flows()).ifPresent(securitySchemeObject::setFlows);
179
180         SecuritySchemePair result = new SecuritySchemePair(key, securitySchemeObject);
181         return Optional.of(result);
182     }
183
184     public void buildSecurityRequirement(
185             io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements, Operation operation) {
186         Optional<List<SecurityRequirement>> requirementsObject = this.getSecurityRequirements(securityRequirements);
187         requirementsObject.ifPresent(requirements -> requirements.stream()
188                 .filter(r -> operation.getSecurity() == null || !operation.getSecurity().contains(r))
189                 .forEach(operation::addSecurityItem));
190     }
191
192     private Optional<OAuthFlows> getOAuthFlows(io.swagger.v3.oas.annotations.security.OAuthFlows oAuthFlows) {
193         if (isEmpty(oAuthFlows))
194             return Optional.empty();
195
196         OAuthFlows oAuthFlowsObject = new OAuthFlows();
197         if (oAuthFlows.extensions().length > 0) {
198             Map<String, Object> extensions = AnnotationsUtils.getExtensions(oAuthFlows.extensions());
199             extensions.forEach(oAuthFlowsObject::addExtension);
200         }
201         getOAuthFlow(oAuthFlows.authorizationCode()).ifPresent(oAuthFlowsObject::setAuthorizationCode);
202         getOAuthFlow(oAuthFlows.clientCredentials()).ifPresent(oAuthFlowsObject::setClientCredentials);
203         getOAuthFlow(oAuthFlows.implicit()).ifPresent(oAuthFlowsObject::setImplicit);
204         getOAuthFlow(oAuthFlows.password()).ifPresent(oAuthFlowsObject::setPassword);
205         return Optional.of(oAuthFlowsObject);
206     }
207
208     private Optional<OAuthFlow> getOAuthFlow(io.swagger.v3.oas.annotations.security.OAuthFlow oAuthFlow) {
209         if (isEmpty(oAuthFlow)) {
210             return Optional.empty();
211         }
212         OAuthFlow oAuthFlowObject = new OAuthFlow();
213         if (StringUtils.isNotBlank(oAuthFlow.authorizationUrl()))
214             oAuthFlowObject.setAuthorizationUrl(propertyResolverUtils.resolve(oAuthFlow.authorizationUrl()));
215
216         if (StringUtils.isNotBlank(oAuthFlow.refreshUrl()))
217             oAuthFlowObject.setRefreshUrl(propertyResolverUtils.resolve(oAuthFlow.refreshUrl()));
218
219         if (StringUtils.isNotBlank(oAuthFlow.tokenUrl()))
220             oAuthFlowObject.setTokenUrl(propertyResolverUtils.resolve(oAuthFlow.tokenUrl()));
221
222         if (oAuthFlow.extensions().length > 0) {
223             Map<String, Object> extensions = AnnotationsUtils.getExtensions(oAuthFlow.extensions());
224             extensions.forEach(oAuthFlowObject::addExtension);
225         }
226         getScopes(oAuthFlow.scopes()).ifPresent(oAuthFlowObject::setScopes);
227         return Optional.of(oAuthFlowObject);
228     }
229
230     private Optional<Scopes> getScopes(OAuthScope[] scopes) {
231         if (isEmpty(scopes))
232             return Optional.empty();
233
234         Scopes scopesObject = new Scopes();
235         Arrays.stream(scopes).forEach(scope -> scopesObject.addString(scope.name(), scope.description()));
236         return Optional.of(scopesObject);
237     }
238
239     private SecurityScheme.In getIn(String value) {
240         return Arrays.stream(SecurityScheme.In.values()).filter(i -> i.toString().equals(value)).findFirst()
241                 .orElse(null);
242     }
243
244     private SecurityScheme.Type getType(String value) {
245         return Arrays.stream(SecurityScheme.Type.values()).filter(i -> i.toString().equals(value)).findFirst()
246                 .orElse(null);
247     }
248
249 }