Skip to content

Commit dc4efed

Browse files
Merge pull request #19 from quarkiverse/issue-13
Add support to Basic Auth, Bearer and API Key
2 parents 195b6b6 + 802f48a commit dc4efed

30 files changed

+2846
-46
lines changed

README.md

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
Quarkus' extension for generation of [Rest Clients](https://quarkus.io/guides/rest-client) based on OpenAPI specification files.
88

9+
This extension is based on the [OpenAPI Generator Tool](https://openapi-generator.tech/).
10+
911
## Getting Started
1012

1113
Add the following dependency to your project's `pom.xml` file:
@@ -47,7 +49,7 @@ Now, create the directory `openapi` under your `src/main/` path and add the Open
4749
To fine tune the configuration for each spec file, add the following entry to your properties file. In this example, our spec file is in `src/main/openapi/petstore.json`:
4850

4951
```properties
50-
quarkus.openapi-generator.spec."petstore.json".base-package=org.acme.openapi
52+
quarkus.openapi-generator.codegen.spec."petstore.json".base-package=org.acme.openapi
5153
```
5254

5355
Note that the file name is used to configure the specific information for each spec.
@@ -96,11 +98,65 @@ public class PetResource {
9698

9799
See the [integration-tests](integration-tests) module for more information of how to use this extension. Please be advised that the extension is on experimental, early development stage.
98100

101+
## Authentication Support
102+
103+
If your OpenAPI specification file has `securitySchemes` [definitions](https://spec.openapis.org/oas/v3.1.0#security-scheme-object), the inner generator will [register `ClientRequestFilter`s providers](https://download.eclipse.org/microprofile/microprofile-rest-client-2.0/microprofile-rest-client-spec-2.0.html#_provider_declaration) for you to implement the given authentication mechanism.
104+
105+
To provide the credentials for your application, you can use the [Quarkus configuration support](https://quarkus.io/guides/config). The configuration key is composed using this pattern: `[base_package].security.auth.[security_scheme_name]/[auth_property_name]`. Where:
106+
107+
- `base_package` is the package name you gave when configuring the code generation using `quarkus.openapi-generator.codegen.spec.[open_api_file].base-package` property.
108+
- `security_scheme_name` is the name of the [security scheme object definition](https://spec.openapis.org/oas/v3.1.0#security-scheme-object) in the OpenAPI file. Given the following excerpt, we have `api_key` and `basic_auth` security schemes:
109+
```json
110+
{
111+
"securitySchemes": {
112+
"api_key": {
113+
"type": "apiKey",
114+
"name": "api_key",
115+
"in": "header"
116+
},
117+
"basic_auth": {
118+
"type": "http",
119+
"scheme": "basic"
120+
}
121+
}
122+
}
123+
```
124+
- `auth_property_name` varies depending on the authentication provider. For example, for Basic Authentication we have `username` and `password`. See the following sections for more details.
125+
126+
> Tip: on production environments you will likely to use [HashiCorp Vault](https://quarkiverse.github.io/quarkiverse-docs/quarkus-vault/dev/index.html) or [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) to provide this information for your application.
127+
128+
### Basic HTTP Authentication
129+
130+
For Basic HTTP Authentication, these are the supported configurations:
131+
132+
| Description | Property Key | Example |
133+
| -------------------- | -------------------------------------------------------------- | ---------------------------------------------------- |
134+
| Username credentials | `[base_package].security.auth.[security_scheme_name]/username` | `org.acme.openapi.security.auth.basic_auth/username` |
135+
| Password credentials | `[base_package].security.auth.[security_scheme_name]/password` | `org.acme.openapi.security.auth.basic_auth/password` |
136+
137+
### Bearer Token Authentication
138+
139+
For Bearer Token Authentication, these are the supported configurations:
140+
141+
| Description | Property Key | Example |
142+
| -------------| ------------------------------------------------------------------ | -------------------------------------------------------- |
143+
| Bearer Token | `[base_package].security.auth.[security_scheme_name]/bearer-token` | `org.acme.openapi.security.auth.bearer/bearer-token` |
144+
145+
### API Key Authentication
146+
147+
Similarly to bearer token, the API Key Authentication also has the token entry key property:
148+
149+
| Description | Property Key | Example |
150+
| -------------| --------------------------------------------------------------| --------------------------------------------------- |
151+
| API Key | `[base_package].security.auth.[security_scheme_name]/api-key` | `org.acme.openapi.security.auth.apikey/api-key` |
152+
153+
The API Key scheme has an additional property that requires where to add the API key in the request token: header, cookie or query. The inner provider takes care of that for you.
154+
99155
## Known Limitations
100156

101157
These are the known limitations of this pre-release version:
102158

103-
- No authentication support (Basic, Bearer, OAuth2)
159+
- No OAuth2 support
104160
- No reactive support
105161
- Only Jackson support
106162

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
package io.quarkiverse.openapi.generator.deployment;
22

3+
import static io.quarkiverse.openapi.generator.deployment.OpenApiGeneratorBuildTimeConfig.CONFIG_BUILD_NAME;
4+
35
import java.util.Map;
46

57
import io.quarkus.runtime.annotations.ConfigItem;
68
import io.quarkus.runtime.annotations.ConfigPhase;
79
import io.quarkus.runtime.annotations.ConfigRoot;
810

9-
@ConfigRoot(phase = ConfigPhase.BUILD_TIME, name = "openapi-generator")
10-
public class OpenApiGeneratorConfiguration {
11-
static final String CONFIG_PREFIX = "quarkus.openapi-generator";
11+
@ConfigRoot(phase = ConfigPhase.BUILD_TIME, name = CONFIG_BUILD_NAME)
12+
public class OpenApiGeneratorBuildTimeConfig {
13+
public static final String CONFIG_BUILD_NAME = "openapi-generator.codegen";
14+
15+
static final String BUILD_TIME_CONFIG_PREFIX = "quarkus." + CONFIG_BUILD_NAME;
1216

1317
/**
1418
* Fine tune the configuration for each OpenAPI spec file in `src/openapi` directory.
1519
* <p>
1620
* The file name is used to index this configuration. For example:
17-
* `quarkus.openapi-generator.spec."myfilespec.json".base-package=org.acme`.
21+
* `quarkus.openapi-generator.codegen.spec."myfilespec.json".base-package=org.acme`.
1822
**/
1923
@ConfigItem(name = "spec")
2024
public Map<String, SpecConfig> specs;

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/OpenApiGeneratorProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class OpenApiGeneratorProcessor {
1111

1212
private static final String FEATURE = "openapi-generator";
1313

14-
OpenApiGeneratorConfiguration configuration;
14+
OpenApiGeneratorBuildTimeConfig configuration;
1515

1616
@BuildStep
1717
void discoverGeneratedApis(BuildProducer<GeneratedOpenApiRestClientBuildItem> restClients,

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/SpecConfig.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.quarkiverse.openapi.generator.deployment;
22

3-
import static io.quarkiverse.openapi.generator.deployment.OpenApiGeneratorConfiguration.CONFIG_PREFIX;
3+
import static io.quarkiverse.openapi.generator.deployment.OpenApiGeneratorBuildTimeConfig.BUILD_TIME_CONFIG_PREFIX;
44

55
import java.nio.file.Path;
66

@@ -11,7 +11,8 @@
1111
public class SpecConfig {
1212
public static final String API_PKG_SUFFIX = ".api";
1313
public static final String MODEL_PKG_SUFFIX = ".model";
14-
static final String BASE_PACKAGE_PROP_FORMAT = CONFIG_PREFIX + ".spec.\"%s\".base-package";
14+
public static final String BUILD_TIME_SPEC_PREFIX_FORMAT = BUILD_TIME_CONFIG_PREFIX + ".spec.\"%s\"";
15+
private static final String BASE_PACKAGE_PROP_FORMAT = "%s.base-package";
1516

1617
/**
1718
* Defines the base package name for the generated classes.
@@ -28,8 +29,20 @@ public String getModelPackage() {
2829
}
2930

3031
public static String getResolvedBasePackageProperty(final Path openApiFilePath) {
32+
return String.format(BASE_PACKAGE_PROP_FORMAT, getBuildTimeSpecPropertyPrefix(openApiFilePath));
33+
}
34+
35+
/**
36+
* Gets the config prefix for a given OpenAPI file in the path.
37+
* For example, given a path like /home/luke/projects/petstore.json, the returned value is
38+
* `quarkus.openapi-generator."petstore.json"`.
39+
*/
40+
public static String getBuildTimeSpecPropertyPrefix(final Path openApiFilePath) {
41+
return String.format(BUILD_TIME_SPEC_PREFIX_FORMAT, getEscapedFileName(openApiFilePath));
42+
}
43+
44+
private static String getEscapedFileName(final Path openApiFilePath) {
3145
final String uriFilePath = openApiFilePath.toUri().toString();
32-
final String fileName = uriFilePath.substring(uriFilePath.lastIndexOf("/") + 1);
33-
return String.format(BASE_PACKAGE_PROP_FORMAT, fileName);
46+
return uriFilePath.substring(uriFilePath.lastIndexOf("/") + 1);
3447
}
3548
}

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
4848
final OpenApiClientGeneratorWrapper generator = new OpenApiClientGeneratorWrapper(
4949
openApiFilePath.normalize(), outDir)
5050
.withApiPackage(basePackage + API_PKG_SUFFIX)
51-
.withModelPackage(basePackage + MODEL_PKG_SUFFIX);
51+
.withModelPackage(basePackage + MODEL_PKG_SUFFIX)
52+
.withBasePackage(basePackage);
5253
generator.generate();
5354
});
5455
} catch (IOException e) {

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ public class QuteTemplatingEngineAdapter extends AbstractTemplatingEngineAdapter
2626
"headerParams.qute",
2727
"pathParams.qute",
2828
"pojo.qute",
29-
"queryParams.qute"
29+
"queryParams.qute",
30+
"auth/compositeAuthenticationProvider.qute",
31+
"auth/authConfig.qute"
3032
};
3133
public final Engine engine;
3234

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ public OpenApiClientGeneratorWrapper(final Path specFilePath, final Path outputD
3232
GlobalSettings.setProperty(CodegenConstants.API_TESTS, FALSE.toString());
3333
GlobalSettings.setProperty(CodegenConstants.MODEL_TESTS, FALSE.toString());
3434
GlobalSettings.setProperty(CodegenConstants.MODEL_DOCS, FALSE.toString());
35-
// generates every Api and Supporting files
36-
// TODO: requires more testing to properly filter the generated classes
35+
// generates every Api and Models
3736
GlobalSettings.setProperty(CodegenConstants.APIS, "");
3837
GlobalSettings.setProperty(CodegenConstants.MODELS, "");
3938
GlobalSettings.setProperty(CodegenConstants.SUPPORTING_FILES, "");
@@ -58,6 +57,11 @@ public OpenApiClientGeneratorWrapper withModelPackage(final String pkg) {
5857
return this;
5958
}
6059

60+
public OpenApiClientGeneratorWrapper withBasePackage(final String pkg) {
61+
this.configurator.setPackageName(pkg);
62+
return this;
63+
}
64+
6165
public List<File> generate() {
6266
return generator.opts(configurator.toClientOptInput()).generate();
6367
}
Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
package io.quarkiverse.openapi.generator.deployment.wrapper;
22

3+
import java.io.File;
4+
5+
import org.openapitools.codegen.SupportingFile;
36
import org.openapitools.codegen.languages.JavaClientCodegen;
7+
import org.openapitools.codegen.utils.ProcessUtils;
48

59
public class QuarkusJavaClientCodegen extends JavaClientCodegen {
610

11+
private static final String AUTH_PACKAGE = "auth";
12+
713
public QuarkusJavaClientCodegen() {
8-
// TODO: immutable properties
14+
// immutable properties
915
this.setDateLibrary(JavaClientCodegen.JAVA8_MODE);
16+
this.setSerializationLibrary(SERIALIZATION_LIBRARY_JACKSON);
1017
this.setTemplateDir("templates");
11-
// we are only interested in the main generated classes
12-
this.projectFolder = "";
13-
this.projectTestFolder = "";
14-
this.sourceFolder = "";
15-
this.testFolder = "";
1618
}
1719

1820
@Override
@@ -23,10 +25,42 @@ public String getName() {
2325
@Override
2426
public void processOpts() {
2527
super.processOpts();
28+
// we are only interested in the main generated classes
29+
this.projectFolder = "";
30+
this.projectTestFolder = "";
31+
this.sourceFolder = "";
32+
this.testFolder = "";
33+
34+
this.replaceWithQuarkusTemplateFiles();
35+
}
36+
37+
private void replaceWithQuarkusTemplateFiles() {
2638
supportingFiles.clear();
39+
40+
// TODO: add others as we go
41+
if (ProcessUtils.hasHttpBasicMethods(this.openAPI) ||
42+
ProcessUtils.hasApiKeyMethods(this.openAPI) ||
43+
ProcessUtils.hasHttpBearerMethods(this.openAPI)) {
44+
supportingFiles.add(
45+
new SupportingFile("auth/compositeAuthenticationProvider.qute",
46+
authFileFolder(),
47+
"CompositeAuthenticationProvider.java"));
48+
supportingFiles.add(
49+
new SupportingFile("auth/authConfig.qute",
50+
authFileFolder(),
51+
"AuthConfiguration.java"));
52+
}
53+
2754
apiTemplateFiles.clear();
2855
apiTemplateFiles.put("api.qute", ".java");
56+
2957
modelTemplateFiles.clear();
3058
modelTemplateFiles.put("model.qute", ".java");
3159
}
60+
61+
public String authFileFolder() {
62+
// we are not using the apiFileFolder since it returns the full path
63+
// we are only interested in the package path
64+
return apiPackage().replace('.', File.separatorChar) + File.separator + AUTH_PACKAGE;
65+
}
3266
}

deployment/src/main/resources/templates/api.qute

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import {imp.import};
55
{/for}
66

77
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
8+
{#if hasAuthMethods}
9+
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
10+
import {package}.auth.CompositeAuthenticationProvider;
11+
{/if}
812

913
import java.io.InputStream;
1014
import java.io.OutputStream;
@@ -23,6 +27,9 @@ import javax.ws.rs.core.MediaType;
2327
{/if}
2428
@Path("{#if useAnnotatedBasePath}{contextPath}{/if}{commonPath}")
2529
@RegisterRestClient
30+
{#if hasAuthMethods}
31+
@RegisterProvider(CompositeAuthenticationProvider.class)
32+
{/if}
2633
public interface {classname} {
2734

2835
{#for op in operations.operation}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package {apiPackage}.auth;
2+
3+
import io.quarkiverse.openapi.generator.providers.AuthProvidersConfig;
4+
import io.smallrye.config.ConfigMapping;
5+
6+
@ConfigMapping(prefix = "{packageName}.security")
7+
public interface AuthConfiguration extends AuthProvidersConfig {
8+
9+
}

0 commit comments

Comments
 (0)