question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

@ApiOperation should inherit authorizations from @Api but doesn't

See original GitHub issue

If I have a Controller like this

@RestController
@Api(authorizations = { @Authorization(value = "oauth")})
public class AResource {

  @GetMapping("/foo")
  @ApiOperation
  public SomeFoo foo() {
    return ...;
  }

  @GetMapping("/bar")
  @ApiOperation(authorizations = {@Authorization(value = "something") })
  public SomeBar bar() {
    return ....;
  }
}

one would expect the resource /foo to require oauth authorization while /bar requires some other form (something). However, it seems that Sprinfox never looks at Api.authorizations.

I suggest springfox should be extended with a new OperationBuilderPlugin that evaluates Api.authorizations. It could look much like the one below, which works in my test setup.

/*
 *
 *  Copyright 2015-2016 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 */

package com.example.demo;

import static com.google.common.base.Strings.*;
import static com.google.common.collect.Lists.*;
import static com.google.common.collect.Maps.*;

import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.Authorization;
import io.swagger.annotations.AuthorizationScope;
import springfox.documentation.builders.AuthorizationScopeBuilder;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
public class ControllerAuthReader implements OperationBuilderPlugin {

  private static final Logger LOG = LoggerFactory.getLogger(ControllerAuthReader.class);

  @Override
  public void apply(final OperationContext context) {

   List<SecurityContext> securityContexts = context.securityContext();

    Map<String, SecurityReference> securityReferences = newHashMap();

    for (SecurityContext each : securityContexts) {
      securityReferences.putAll(
          FluentIterable.from(each.securityForOperation(context))
          .uniqueIndex(byReferenceName()));
    }

    Optional<Api> controllerAnnotation = context.findControllerAnnotation(Api.class);
    Optional<ApiOperation> apiOperationAnnotation = context.findAnnotation(ApiOperation.class);

    if (controllerAnnotation.isPresent()) {
      // ApiOperation.authorizations takes precedence over Api.authorizations
      if (!authorizationReferences(controllerAnnotation.get()).isEmpty() 
          && (!apiOperationAnnotation.isPresent() || authorizationReferences(apiOperationAnnotation.get()).isEmpty())) {
        List<SecurityReference> securityReferenceOverrides = newArrayList();
        
        for (Authorization authorization : authorizationReferences(controllerAnnotation.get())) {
          // The rest is copied verbatim from OperationAuthReader - it should rather be made reusable
          String value = authorization.value();
          AuthorizationScope[] scopes = authorization.scopes();
          List<springfox.documentation.service.AuthorizationScope> authorizationScopeList = newArrayList();
          for (AuthorizationScope authorizationScope : scopes) {
            String description = authorizationScope.description();
            String scope = authorizationScope.scope();
            // @Authorization has a default blank authorization scope, which we need to
            // ignore in the case of api keys.
            if (!isNullOrEmpty(scope)) {
              authorizationScopeList.add(
                  new AuthorizationScopeBuilder()
                      .scope(scope)
                      .description(description)
                      .build());
            }
          }
          springfox.documentation.service.AuthorizationScope[] authorizationScopes
              = authorizationScopeList
              .toArray(new springfox.documentation.service.AuthorizationScope[0]);
          SecurityReference securityReference =
              SecurityReference.builder()
                  .reference(value)
                  .scopes(authorizationScopes)
                  .build();
          securityReferenceOverrides.add(securityReference);
        }
        securityReferences.putAll(FluentIterable.from(securityReferenceOverrides)
            .uniqueIndex(byReferenceName()));
      }
    }
    LOG.debug("Authorization count {} for method {}", securityReferences.size(), context.getName());
    context.operationBuilder().authorizations(securityReferences.values());
  }

  private Function<SecurityReference, String> byReferenceName() {
    return new Function<SecurityReference, String>() {
      @Override
      public String apply(final SecurityReference input) {
        return input.getReference();
      }
    };
  }

  private FluentIterable<Authorization> authorizationReferences(final Api apiAnnotation) {
    return FluentIterable.from(apiAnnotation.authorizations())
        .filter(nonEmptyAuthorization());
  }
  
  private FluentIterable<Authorization> authorizationReferences(final ApiOperation apiOperationAnnotation) {
    return FluentIterable.from(apiOperationAnnotation.authorizations())
        .filter(nonEmptyAuthorization());
  }

  private Predicate<Authorization> nonEmptyAuthorization() {
    return new Predicate<Authorization>() {
      @Override
      public boolean apply(final Authorization input) {
        return !Strings.isNullOrEmpty(input.value());
      }
    };
  }

  @Override
  public boolean supports(final DocumentationType delimiter) {
    return SwaggerPluginSupport.pluginDoesApply(delimiter);
  }
}

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:1
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
anoopgarlapaticommented, Mar 11, 2019

Any progress on this? I am facing the same issue. Defining authorization scopes for each ApiOperation is tedious and we would like to do that at Api resource level.

0reactions
dilipkrishcommented, Aug 3, 2020

This is changed after the support for open api went in. Open to accepting PRs for this but this is deprioritized since we will now be using @OpenAPIDefinition

Read more comments on GitHub >

github_iconTop Results From Across the Web

Swagger UI does not display response models from custom ...
1 Answer 1 · I am using springfox-swagger2 v3. · And this is not the answer I was looking for. · @Dipu I...
Read more >
Carbon Black Cloud: What are the differences between API ...
This type of permissions rule is inherited by child processes, and should be very limited in use. Performs any API operation - the...
Read more >
Make Swagger UI usable even with class inheritance ... - Wei He
In this article, I would like to share my “code first” experience with Swagger during implementing a REST API which data models have...
Read more >
OpenAPI Specification - Version 2.0 - Swagger
Version 2.0 specification defines a set of files required to describe an API. These files can then be used by the Swagger-UI project...
Read more >
Swagger Annotations for Rest API Documentation - Java Guides
Only classes that are annotated with @Api will be scanned by Swagger. ... @ApiOperation(value = "Add a new pet to the store", authorizations...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found