@ -86,6 +86,9 @@ public class SwaggerConfiguration {
public static final String LOGIN_ENDPOINT = "/api/auth/login" ;
public static final String REFRESH_TOKEN_ENDPOINT = "/api/auth/token" ;
private static final String LOGIN_PASSWORD_SCHEME = "HTTP login form" ;
private static final String API_KEY_SCHEME = "API key form" ;
private static final ApiResponses loginResponses = loginResponses ( ) ;
private static final ApiResponses defaultErrorResponses = defaultErrorResponses ( false ) ;
private static final ApiResponses defaultPostErrorResponses = defaultErrorResponses ( true ) ;
@ -142,14 +145,28 @@ public class SwaggerConfiguration {
. license ( license )
. version ( apiVersion ) ;
SecurityScheme security Scheme = new SecurityScheme ( )
SecurityScheme loginPassword Scheme = new SecurityScheme ( )
. type ( SecurityScheme . Type . HTTP )
. description ( "Enter Username / Password" )
. scheme ( "loginPassword" )
. bearerFormat ( "/api/auth/login|X-Authorization" ) ;
SecurityScheme apiKeyScheme = new SecurityScheme ( )
. type ( SecurityScheme . Type . APIKEY )
. name ( "X-Authorization" )
. in ( SecurityScheme . In . HEADER )
. description ( "" "
Enter the API key value with ' ApiKey ' prefix in format : * * ApiKey < your_api_key_value > * *
Example : * * ApiKey tb_5te51SkLRYpjGrujUGwqkjFvooWBlQpVe2An2Dr3w13wjfxDW * *
< br > * * NOTE * * : Use only ONE authentication method at a time . If both are authorized , JWT auth takes the priority . < br >
"" " ) ;
var openApi = new OpenAPI ( )
. components ( new Components ( ) . addSecuritySchemes ( "HTTP login form" , securityScheme ) )
. components ( new Components ( )
. addSecuritySchemes ( LOGIN_PASSWORD_SCHEME , loginPasswordScheme )
. addSecuritySchemes ( API_KEY_SCHEME , apiKeyScheme ) )
. info ( info ) ;
addDefaultSchemas ( openApi ) ;
addLoginOperation ( openApi ) ;
@ -198,13 +215,14 @@ public class SwaggerConfiguration {
operation . summary ( "Login method to get user JWT token data" ) ;
operation . description ( "" "
Login method used to authenticate user and get JWT token data .
Value of the response * * token * * field can be used as * * X - Authorization * * header value :
` X - Authorization : Bearer $JWT_TOKEN_VALUE ` . "" " ) ;
var requestBody = new RequestBody ( ) . description ( "Login request" )
. content ( new Content ( ) . addMediaType ( APPLICATION_JSON_VALUE ,
new MediaType ( ) . schema ( new Schema < LoginRequest > ( ) . $ref ( "#/components/schemas/LoginRequest" ) ) ) ) ;
new MediaType ( ) . schema ( new Schema < LoginRequest > ( ) . $ref ( "#/components/schemas/LoginRequest" ) ) ) ) ;
operation . requestBody ( requestBody ) ;
operation . responses ( loginResponses ) ;
@ -218,11 +236,11 @@ public class SwaggerConfiguration {
var operation = new Operation ( ) ;
operation . summary ( "Refresh user JWT token data" ) ;
operation . description ( "" "
Method to refresh JWT token . Provide a valid refresh token to get a new JWT token .
The response contains a new token that can be used for authorization .
` X - Authorization : Bearer $JWT_TOKEN_VALUE ` "" " ) ;
Method to refresh JWT token . Provide a valid refresh token to get a new JWT token .
The response contains a new token that can be used for authorization .
` X - Authorization : Bearer $JWT_TOKEN_VALUE ` "" " ) ;
var requestBody = new RequestBody ( ) . description ( "Refresh token request" )
. content ( new Content ( ) . addMediaType ( APPLICATION_JSON_VALUE ,
@ -291,8 +309,9 @@ public class SwaggerConfiguration {
return ( routerOperation , handlerMethod ) - > {
String [ ] pNames = localSpringDocParameterNameDiscoverer . getParameterNames ( handlerMethod . getMethod ( ) ) ;
String [ ] reflectionParametersNames = Arrays . stream ( handlerMethod . getMethod ( ) . getParameters ( ) ) . map ( java . lang . reflect . Parameter : : getName ) . toArray ( String [ ] : : new ) ;
if ( pNames = = null | | Arrays . stream ( pNames ) . anyMatch ( Objects : : isNull ) )
if ( pNames = = null | | Arrays . stream ( pNames ) . anyMatch ( Objects : : isNull ) ) {
pNames = reflectionParametersNames ;
}
MethodParameter [ ] parameters = handlerMethod . getMethodParameters ( ) ;
List < String > requestParams = new ArrayList < > ( ) ;
for ( var i = 0 ; i < parameters . length ; i + + ) {
@ -324,26 +343,25 @@ public class SwaggerConfiguration {
}
private OpenApiCustomizer customOpenApiCustomizer ( ) {
var loginForm = new SecurityRequirement ( ) . addList ( "HTTP login form" , Arrays . asList (
Authority . SYS_ADMIN . name ( ) ,
Authority . TENANT_ADMIN . name ( ) ,
Authority . CUSTOMER_USER . name ( )
) ) ;
var loginRequirement = createSecurityRequirement ( LOGIN_PASSWORD_SCHEME ) ;
var apiKeyRequirement = createSecurityRequirement ( API_KEY_SCHEME ) ;
return openAPI - > {
var paths = openAPI . getPaths ( ) ;
paths . entrySet ( ) . stream ( ) . peek ( entry - > {
securityCustomization ( loginForm , entry ) ;
if ( ! entry . getKey ( ) . equals ( LOGIN_ENDPOINT ) ) {
defaultErrorResponsesCustomization ( entry . getValue ( ) ) ;
}
} ) . map ( this : : tagsCustomization ) . filter ( Objects : : nonNull ) . distinct ( ) . sorted ( Comparator . comparing ( Tag : : getName ) ) . forEach ( openAPI : : addTagsItem ) ;
paths . entrySet ( ) . stream ( )
. peek ( entry - > {
securityCustomization ( entry , loginRequirement , apiKeyRequirement ) ;
if ( ! entry . getKey ( ) . equals ( LOGIN_ENDPOINT ) ) {
defaultErrorResponsesCustomization ( entry . getValue ( ) ) ;
}
} )
. map ( this : : extractTagFromPath ) . filter ( Objects : : nonNull ) . distinct ( ) . sorted ( Comparator . comparing ( Tag : : getName ) ) . forEach ( openAPI : : addTagsItem ) ;
var pathItemsByTags = new TreeMap < String , Map < String , PathItem > > ( ) ;
paths . forEach ( ( k , v ) - > {
var tagItem = tagItemFromPathItem ( v ) ;
if ( tagItem ! = null ) {
var pathItemMap = pathItemsByTags . computeIfAbsent ( tagItem , k1 - > new TreeMap < > ( ) ) ;
pathItemMap . put ( k , v ) ;
pathItemsByTags . computeIfAbsent ( tagItem , k1 - > new TreeMap < > ( ) ) . put ( k , v ) ;
}
} ) ;
var sortedPaths = new Paths ( ) ;
@ -357,13 +375,17 @@ public class SwaggerConfiguration {
} ;
}
private SecurityRequirement createSecurityRequirement ( String schemeName ) {
return new SecurityRequirement ( ) . addList ( schemeName , Arrays . asList (
Authority . SYS_ADMIN . name ( ) ,
Authority . TENANT_ADMIN . name ( ) ,
Authority . CUSTOMER_USER . name ( )
) ) ;
}
private Tag tagsCustomization ( Map . Entry < String , PathItem > entry ) {
var tagItem = tagItemFromPathItem ( entry . getValue ( ) ) ;
if ( tagItem ! = null ) {
return tagFromTagItem ( tagItem ) ;
}
return null ;
private Tag extractTagFromPath ( Map . Entry < String , PathItem > entry ) {
var tagName = tagItemFromPathItem ( entry . getValue ( ) ) ;
return tagName ! = null ? tagFromTagItem ( tagName ) : null ;
}
private String tagItemFromPathItem ( PathItem item ) {
@ -383,17 +405,20 @@ public class SwaggerConfiguration {
StringBuilder sb = new StringBuilder ( ) ;
for ( String word : words ) {
sb . append ( word . substring ( 0 , 1 ) . toUpperCase ( ) ) ;
sb . append ( word . substring ( 1 ) . toLowerCase ( ) ) ;
sb . append ( " " ) ;
if ( ! word . isEmpty ( ) ) {
sb . append ( word . substring ( 0 , 1 ) . toUpperCase ( ) ) ;
sb . append ( word . substring ( 1 ) . toLowerCase ( ) ) ;
sb . append ( " " ) ;
}
}
return new Tag ( ) . name ( tagItem ) . description ( sb . toString ( ) . trim ( ) ) ;
}
private void defaultErrorResponsesCustomization ( PathItem pathItem ) {
pathItem . readOperationsMap ( ) . forEach ( ( ( httpMethod , operation ) - > {
pathItem . readOperationsMap ( ) . forEach ( ( httpMethod , operation ) - > {
var errorResponses = httpMethod . equals ( PathItem . HttpMethod . POST ) ? defaultPostErrorResponses : defaultErrorResponses ;
var responses = operation . getResponses ( ) ;
if ( responses = = null ) {
responses = errorResponses ;
@ -406,16 +431,19 @@ public class SwaggerConfiguration {
} ) ;
}
operation . setResponses ( responses ) ;
} ) ) ;
} ) ;
}
private void securityCustomization ( SecurityRequirement loginForm , Map . Entry < String , PathItem > entry ) {
private void securityCustomization ( Map . Entry < String , PathItem > entry , SecurityRequirement jwtBearerRequirement , SecurityRequirement apiKeyRequirement ) {
var path = entry . getKey ( ) ;
if ( path . matches ( securityPathRegex ) & & ! path . matches ( nonSecurityPathRegex ) & & ! path . equals ( LOGIN_ENDPOINT ) ) {
if ( path . matches ( securityPathRegex ) & & ! path . matches ( nonSecurityPathRegex ) & & ! path . equals ( LOGIN_ENDPOINT ) & & ! path . equals ( REFRESH_TOKEN_ENDPOINT ) ) {
entry . getValue ( )
. readOperationsMap ( )
. values ( )
. forEach ( operation - > operation . addSecurityItem ( loginForm ) ) ;
. forEach ( operation - > {
operation . addSecurityItem ( jwtBearerRequirement ) ;
operation . addSecurityItem ( apiKeyRequirement ) ;
} ) ;
}
}
@ -430,6 +458,7 @@ public class SwaggerConfiguration {
private static ApiResponses defaultErrorResponses ( boolean isPost ) {
ApiResponses apiResponses = new ApiResponses ( ) ;
apiResponses . addApiResponse ( "400" , errorResponse ( "400" , "Bad Request" ,
ThingsboardErrorResponse . of ( isPost ? "Invalid request body" : "Invalid UUID string: 123" , ThingsboardErrorCode . BAD_REQUEST_PARAMS , HttpStatus . BAD_REQUEST ) ) ) ;
@ -465,8 +494,7 @@ public class SwaggerConfiguration {
ThingsboardErrorResponse . of ( "Authentication failed" , ThingsboardErrorCode . AUTHENTICATION , HttpStatus . UNAUTHORIZED ) )
)
) ) ;
var credentialsExpiredSchema = new Schema < ThingsboardCredentialsExpiredResponse > ( ) ;
credentialsExpiredSchema . $ref ( "#/components/schemas/ThingsboardCredentialsExpiredResponse" ) ;
var credentialsExpiredSchema = new Schema < ThingsboardCredentialsExpiredResponse > ( ) . $ref ( "#/components/schemas/ThingsboardCredentialsExpiredResponse" ) ;
apiResponses . addApiResponse ( "401 " , errorResponse ( "Unauthorized (**Expired credentials**)" ,
Map . of (
"credentials-expired" , errorExample ( "Expired credentials" ,
@ -482,15 +510,13 @@ public class SwaggerConfiguration {
}
private static ApiResponse errorResponse ( String description , Map < String , Example > examples ) {
var schema = new Schema < ThingsboardErrorResponse > ( ) ;
schema . $ref ( "#/components/schemas/ThingsboardErrorResponse" ) ;
var schema = new Schema < ThingsboardErrorResponse > ( ) . $ref ( "#/components/schemas/ThingsboardErrorResponse" ) ;
return errorResponse ( description , examples , schema ) ;
}
private static ApiResponse errorResponse ( String description , Map < String , Example > examples , Schema < ? extends ThingsboardErrorResponse > errorResponseSchema ) {
MediaType mediaType = new MediaType ( ) . schema ( errorResponseSchema ) ;
mediaType . setExamples ( examples ) ;
Content content = new Content ( ) . addMediaType ( org . springframework . http . MediaType . APPLICATION_JSON_VALUE , mediaType ) ;
MediaType mediaType = new MediaType ( ) . schema ( errorResponseSchema ) . examples ( examples ) ;
Content content = new Content ( ) . addMediaType ( org . springframework . http . MediaType . APPLICATION_JSON_VALUE , mediaType ) ;
return new ApiResponse ( ) . description ( description ) . content ( content ) ;
}