Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

v2 improves security code and documentation #137

Merged
merged 25 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
eb3aaca
Adds readme security section
spacether Mar 22, 2023
40f8137
Adds security sample
spacether Mar 23, 2023
d2175d3
Adds files for python testing
spacether Mar 23, 2023
7879fc0
petstore regenerated to delete unused api path files
spacether Mar 23, 2023
c6fc4db
Adds security section to the docs
spacether Mar 23, 2023
44747b2
Generates root security requirements objects
spacether Mar 23, 2023
49b3b98
Generates security requirement objects
spacether Mar 23, 2023
67fac78
Passes through and uses security_index
spacether Mar 25, 2023
486aa56
Operation docs now show security code from root
spacether Mar 25, 2023
a44e36b
Renames auth_info to security_scheme_info
spacether Mar 25, 2023
22d0d82
Adds test_endpoint_call_lacks_security
spacether Mar 26, 2023
2ceabac
Adds test_endpoint_call_lacks_security, more code
spacether Mar 26, 2023
70d9941
Adds test_endpoint_call_contains_security
spacether Mar 26, 2023
47f20c5
Fixes operation response type, remves typing union
spacether Mar 26, 2023
aa10d2f
Improves multiple response and parameter imports in operation files
spacether Mar 26, 2023
391397b
Fixes petstore tests
spacether Mar 26, 2023
1c17ba9
Adds tests of all securities in the endpoints, secuirty_index passed in
spacether Mar 26, 2023
38fd1da
Adds ServerIndexInfo to allow configuration of servers by path or ope…
spacether Mar 26, 2023
0808949
Regnerates petstore, fixes petstore test
spacether Mar 26, 2023
92807d5
Adds SecurityIndexInfo
spacether Mar 26, 2023
f9d231a
Adds security_index_info to allow users to set a default security ind…
spacether Mar 27, 2023
4c3b7ba
Adds test_endpoint_call_contains_security_index1_from_endpoint_config
spacether Mar 27, 2023
0fe422f
Adds CI invocation
spacether Mar 27, 2023
91bc726
Regenerates samples
spacether Mar 27, 2023
2d76777
Sample regen with unit test fix
spacether Mar 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions CI/circle_parallel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ elif [ "$NODE_INDEX" = "4" ]; then
(cd samples/openapi3/client/petstore/python && make test)
(cd samples/openapi3/client/3_0_3_unit_test/python && make test)
(cd samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python && make test)
(cd samples/openapi3/client/features/security/python && make test)

else
echo "Running node $NODE_INDEX"
Expand Down
5 changes: 5 additions & 0 deletions bin/configs/python_security.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
generatorName: python
outputDir: samples/openapi3/client/features/security/python
inputSpec: modules/openapi-json-schema-generator/src/test/resources/3_0/security.yaml
additionalProperties:
packageName: this_package
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public interface CodegenConfig {

CodegenTag fromTag(String name, String description);

List<HashMap<String, CodegenSecurityRequirementValue>> fromSecurity(List<SecurityRequirement> security, String jsonPath);

CodegenOperation fromOperation(Operation operation, String jsonPath);

CodegenKey getKey(String key);
Expand Down Expand Up @@ -214,6 +216,8 @@ public interface CodegenConfig {

String toServerFilename(String baseName);

String toSecurityRequirementObjectFilename(String baseName);

String getCamelCaseServer(String baseName);

String toModelImport(String refClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class CodegenConstants {
/* System Properties */
// NOTE: We may want to move these to a separate class to avoid confusion or modification.
public static final String SERVERS = "servers";

public static final String SECURITY = "security";
public static final String APIS = "apis";
public static final String MODELS = "models";
public static final String CONTENT = "content";
Expand Down Expand Up @@ -243,7 +245,7 @@ public class CodegenConstants {

public static enum PARAM_NAMING_TYPE {camelCase, PascalCase, snake_case, original}

public static enum JSON_PATH_LOCATION_TYPE {SCHEMA, REQUEST_BODY, PARAMETER, RESPONSE, HEADER, CONTENT, CONTENT_TYPE, HEADERS, PARAMETERS, RESPONSES, REQUEST_BODIES, SCHEMAS, PATHS, PATH, COMPONENTS, OPERATION, SECURITY_SCHEMES, SECURITY_SCHEME, SERVERS, SERVER, API_ROOT_FOLDER, API_PATH, API_TAG, API_PATHS, API_TAGS}
public static enum JSON_PATH_LOCATION_TYPE {SCHEMA, REQUEST_BODY, PARAMETER, RESPONSE, HEADER, CONTENT, CONTENT_TYPE, HEADERS, PARAMETERS, RESPONSES, REQUEST_BODIES, SCHEMAS, PATHS, PATH, COMPONENTS, OPERATION, SECURITY_SCHEMES, SECURITY_SCHEME, SERVERS, SERVER, API_ROOT_FOLDER, API_PATH, API_TAG, API_PATHS, API_TAGS, SECURITY, SECURITIES}

public static enum MODEL_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, original}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,11 @@ public String toServerFilename(String basename) {
return toModuleFilename(basename);
}

@Override
public String toSecurityRequirementObjectFilename(String basename) {
return toModuleFilename(basename);
}

@Override
public String getCamelCaseServer(String basename) {
return toModelName(basename);
Expand Down Expand Up @@ -2639,15 +2644,7 @@ else if (one.required)
return 1;
});
}
List<HashMap<String, CodegenSecurityRequirementValue>> security = null;
List<SecurityRequirement> securities = operation.getSecurity();
if (securities != null && !securities.isEmpty()) {
security = new ArrayList<>();
for (SecurityRequirement originalSecurityRequirement: securities) {
HashMap<String, CodegenSecurityRequirementValue> securityRequirement = fromSecurityRequirement(originalSecurityRequirement, jsonPath + "/security");
security.add(securityRequirement);
}
}
List<HashMap<String, CodegenSecurityRequirementValue>> security = fromSecurity(operation.getSecurity(), jsonPath + "/security");

ExternalDocumentation externalDocs = operation.getExternalDocs();
CodegenKey jsonPathPiece = getKey(pathPieces[pathPieces.length-1]);
Expand Down Expand Up @@ -2681,12 +2678,28 @@ else if (one.required)
jsonPathPiece);
}

@Override
public List<HashMap<String, CodegenSecurityRequirementValue>> fromSecurity(List<SecurityRequirement> security, String jsonPath) {
if (security == null) {
return null;
}
List securityRequirements = new ArrayList<>();
int i = 0;
for (SecurityRequirement specSecurityRequirement: security) {
HashMap<String, CodegenSecurityRequirementValue> securityRequirement = fromSecurityRequirement(specSecurityRequirement, jsonPath+ "/" + i);
securityRequirements.add(securityRequirement);
i++;
}
return securityRequirements;
}

/**
* Convert OAS Response object to Codegen Response object
*
* @param response OAS Response object
* @return Codegen Response object
*/
@Override
public CodegenResponse fromResponse(ApiResponse response, String sourceJsonPath) {
if (response == null) {
String msg = "response in fromResponse cannot be null!";
Expand Down Expand Up @@ -3453,6 +3466,9 @@ private void updatePathsFilepath(String[] pathPieces) {
if (pathPieces[4].equals("servers")) {
// #/paths/somePath/get/servers/someServer
pathPieces[5] = toServerFilename(pathPieces[5]);
} else if (pathPieces[4].equals("security")) {
// #/paths/somePath/get/security/0
pathPieces[5] = toSecurityRequirementObjectFilename(pathPieces[5]);
} else if (pathPieces[4].equals("responses")) {
// #/paths/user_login/get/responses/200 -> 200 -> response_200 -> length 6
pathPieces[5] = toResponseModuleName(pathPieces[5]);
Expand Down Expand Up @@ -3499,6 +3515,13 @@ private void updateServersFilepath(String[] pathPieces) {
pathPieces[2] = toServerFilename(pathPieces[2]);
}

private void updateSecurityFilepath(String[] pathPieces) {
if (pathPieces.length < 3) {
return;
}
pathPieces[2] = toSecurityRequirementObjectFilename(pathPieces[2]);
}

private void updateApisFilepath(String[] pathPieces) {
// #/apis
// #/apis/tags
Expand Down Expand Up @@ -3526,6 +3549,8 @@ public String getFilepath(String jsonPath) {
updatePathsFilepath(pathPieces);
} else if (jsonPath.startsWith("#/servers")) {
updateServersFilepath(pathPieces);
} else if (jsonPath.startsWith("#/security")) {
updateSecurityFilepath(pathPieces);
} else if (jsonPath.startsWith("#/apis")) {
// this is a fake json path that the code generates and uses to generate apis
updateApisFilepath(pathPieces);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.openapijsonschematools.codegen.model.CodegenRequestBody;
import org.openapijsonschematools.codegen.model.CodegenResponse;
import org.openapijsonschematools.codegen.model.CodegenSchema;
import org.openapijsonschematools.codegen.model.CodegenSecurityRequirementValue;
import org.openapijsonschematools.codegen.model.CodegenSecurityScheme;
import org.openapijsonschematools.codegen.model.CodegenServer;
import org.openapijsonschematools.codegen.model.CodegenTag;
Expand Down Expand Up @@ -437,7 +438,7 @@ private void generateFiles(List<List<Object>> processTemplateToFileInfos, boolea
}
}

private void generatePathItem(List<File> files, CodegenKey pathKey, CodegenPathItem pathItem, String jsonPath, List<CodegenServer> servers) {
private void generatePathItem(List<File> files, CodegenKey pathKey, CodegenPathItem pathItem, String jsonPath, List<CodegenServer> servers, List<HashMap<String, CodegenSecurityRequirementValue>> security) {
Map<String, Object> pathTemplateInfo = new HashMap<>();
pathTemplateInfo.put("pathModule", pathKey.snakeCase);
pathTemplateInfo.put("apiClassName", pathKey.camelCase);
Expand All @@ -458,6 +459,7 @@ private void generatePathItem(List<File> files, CodegenKey pathKey, CodegenPathI
endpointMap.put("operation", operation);
endpointMap.put("pathItem", pathItem);
endpointMap.put("httpMethod", httpMethod);
endpointMap.put("security", security);
generateXs(files, operationJsonPath, CodegenConstants.JSON_PATH_LOCATION_TYPE.OPERATION, CodegenConstants.APIS, endpointMap, true);

// operation docs
Expand All @@ -474,6 +476,7 @@ private void generatePathItem(List<File> files, CodegenKey pathKey, CodegenPathI
endpointInfo.put("path", pathKey);
endpointInfo.put("pathItem", pathItem);
endpointInfo.put("servers", servers);
endpointInfo.put("security", security);
endpointInfo.put("packageName", config.packageName());
endpointInfo.put("apiPackage", config.apiPackage());
endpointInfo.put("tag", tag);
Expand All @@ -487,6 +490,12 @@ private void generatePathItem(List<File> files, CodegenKey pathKey, CodegenPathI
}
}

// paths.some_path.security.security_requirement_0.py
if (operation.security != null) {
String securityJsonPath = operationJsonPath + "/security";
generateSecurity(files, operation.security, securityJsonPath);
}

// paths.some_path.post.request_body.py, only written if there is no refModule
if (operation.requestBody != null) {
String requestBodyJsonPath = operationJsonPath + "/requestBody";
Expand Down Expand Up @@ -545,7 +554,7 @@ private void generatePathItem(List<File> files, CodegenKey pathKey, CodegenPathI
}
}

private void generatePaths(List<File> files, TreeMap<CodegenKey, CodegenPathItem> paths, List<CodegenServer> servers) {
private void generatePaths(List<File> files, TreeMap<CodegenKey, CodegenPathItem> paths, List<CodegenServer> servers, List<HashMap<String, CodegenSecurityRequirementValue>> security) {
if (paths == null || paths.isEmpty()) {
LOGGER.info("Skipping generation of paths because the specification document lacks them.");
return;
Expand All @@ -558,13 +567,12 @@ private void generatePaths(List<File> files, TreeMap<CodegenKey, CodegenPathItem
String pathsJsonPath = "#/paths";
generateXs(files, pathsJsonPath, CodegenConstants.JSON_PATH_LOCATION_TYPE.PATHS, CodegenConstants.APIS, null, true);

TreeMap<CodegenKey, CodegenPathItem> codegenPaths = new TreeMap<>();
for (Map.Entry<CodegenKey, CodegenPathItem> entry: paths.entrySet()) {
CodegenKey pathKey = entry.getKey();
CodegenPathItem pathItem = entry.getValue();
String jsonPath = "#/paths/" + ModelUtils.encodeSlashes(pathKey.original);

generatePathItem(files, pathKey, pathItem, jsonPath, servers);
generatePathItem(files, pathKey, pathItem, jsonPath, servers, security);
}
}

Expand Down Expand Up @@ -1251,7 +1259,8 @@ Map<String, Object> buildSupportFileBundle(
TreeMap<String, CodegenParameter> parameters,
TreeMap<String, CodegenSecurityScheme> securitySchemes,
List<CodegenServer> servers,
TreeMap<CodegenKey, CodegenPathItem> paths) {
TreeMap<CodegenKey, CodegenPathItem> paths,
List<HashMap<String, CodegenSecurityRequirementValue>> security) {

Map<String, Object> bundle = new HashMap<>(config.additionalProperties());
bundle.put("apiPackage", config.apiPackage());
Expand Down Expand Up @@ -1292,6 +1301,7 @@ Map<String, Object> buildSupportFileBundle(
bundle.put("servers", servers);
bundle.put("hasServers", hasServers); // also true if there are no root servers but there are pathItem/operation servers
bundle.put("paths", paths);
bundle.put("security", security);
bundle.put("apiFolder", config.apiPackage().replace('.', File.separatorChar));
bundle.put("modelPackage", config.modelPackage());
bundle.put("library", config.getLibrary());
Expand Down Expand Up @@ -1345,7 +1355,7 @@ private void generateServers(List<File> files, List<CodegenServer> servers, Stri
}
}

private TreeMap<String, CodegenTag> generateTags() {
private TreeMap<String, CodegenTag> getTags() {
List<Tag> specTags = openAPI.getTags();
if (specTags == null) {
return null;
Expand All @@ -1360,6 +1370,27 @@ private TreeMap<String, CodegenTag> generateTags() {
return tags;
}

private void generateSecurity(List<File> files, List<HashMap<String, CodegenSecurityRequirementValue>> security, String jsonPath) {
if (security == null || security.isEmpty()) {
return;
}
if (!generateApis) {
LOGGER.info("Skipping generation of security because generateApis is set to false.");
return;
}
generateXs(files, jsonPath, CodegenConstants.JSON_PATH_LOCATION_TYPE.SECURITIES, CodegenConstants.SECURITY, null, true);

int i = 0;
for (HashMap<String, CodegenSecurityRequirementValue> securityRequirementObject: security) {
Map<String, Object> templateData = new HashMap<>();
templateData.put("packageName", config.packageName());
templateData.put("securityRequirementObject", securityRequirementObject);
String serverJsonPath = jsonPath + "/" + i;
generateXs(files, serverJsonPath, CodegenConstants.JSON_PATH_LOCATION_TYPE.SECURITY, CodegenConstants.SECURITY, templateData, true);
i++;
}
}

@Override
public List<File> generate() {
if (openAPI == null) {
Expand Down Expand Up @@ -1396,7 +1427,7 @@ public List<File> generate() {

List<File> files = new ArrayList<>();
// tags
TreeMap<String, CodegenTag> tags = generateTags();
TreeMap<String, CodegenTag> tags = getTags();
// components.schemas / models
TreeMap<String, CodegenSchema> schemas = generateSchemas(files);
// components.requestBodies
Expand All @@ -1409,6 +1440,9 @@ public List<File> generate() {
TreeMap<String, CodegenParameter> parameters = generateParameters(files);
// components.securitySchemes
TreeMap<String, CodegenSecurityScheme> securitySchemes = generateSecuritySchemes(files);
// security
List<HashMap<String, CodegenSecurityRequirementValue>> security = config.fromSecurity(openAPI.getSecurity(), "#/security");
generateSecurity(files, security, "#/security");

boolean schemasExist = (schemas != null && !schemas.isEmpty());
boolean requestBodiesExist = (requestBodies != null && !requestBodies.isEmpty());
Expand All @@ -1424,13 +1458,13 @@ public List<File> generate() {
List<CodegenServer> servers = config.fromServers(openAPI.getServers(), serversJsonPath);
// paths
TreeMap<CodegenKey, CodegenPathItem> paths = config.fromPaths(openAPI.getPaths());
generatePaths(files, paths, servers);
generatePaths(files, paths, servers, security);
generateServers(files, servers, serversJsonPath);
// apis
generateApis(files, paths);

// supporting files
Map<String, Object> bundle = buildSupportFileBundle(schemas, requestBodies, responses, headers, parameters, securitySchemes, servers, paths);
Map<String, Object> bundle = buildSupportFileBundle(schemas, requestBodies, responses, headers, parameters, securitySchemes, servers, paths, security);
generateSupportingFiles(files, bundle);

if (dryRun) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,19 @@ public void processOpts() {
put("components/schemas/__init__schema.hbs", File.separatorChar + "__init__.py");
}}
);
jsonPathTemplateFiles.put(
CodegenConstants.JSON_PATH_LOCATION_TYPE.SECURITIES,
new HashMap<String, String>() {{
put("__init__.hbs", File.separatorChar + "__init__.py");
}}
);
jsonPathTemplateFiles.put(
CodegenConstants.JSON_PATH_LOCATION_TYPE.SECURITY,
new HashMap<String, String>() {{
put("security/security.hbs", ".py");
}}
);

jsonPathTemplateFiles.put(
CodegenConstants.JSON_PATH_LOCATION_TYPE.PATHS,
new HashMap<String, String>() {{
Expand Down Expand Up @@ -1732,6 +1745,11 @@ public String toServerFilename(String basename) {
return "server_" + basename;
}

@Override
public String toSecurityRequirementObjectFilename(String basename) {
return "security_requirement_object_" + basename;
}

@Override
public String getCamelCaseServer(String basename) {
return "Server" + basename;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ server_index | Class | Description
{{@key}} | [{{jsonPathPiece.camelCase}}](docs/servers/{{jsonPathPiece.snakeCase}}.md) |{{#if description}} {{description}}{{/if}}
{{/each}}
{{/if}}
{{#if security}}

## Security

Set auth info by setting ApiConfiguration.security_scheme_info to a dict where the
key is the below security scheme quoted name, and the value is an instance of the linked
component security scheme class. See how to do this in the endpoint code sample.

| Security Index | Security Scheme to Scope Names |
| -------------- | ------------------------------ |
{{#each security}}
| {{@key}} | {{#eq this.size 0}}no security{{else}}{{#each this}}["{{{@key}}}"](docs/components/security_schemes/{{this.refInfo.refModule}}.md) {{this.scopeNames}}<br>{{/each}}{{/eq}} |
{{/each}}
{{/if}}
{{#if paths}}

## Endpoints
Expand Down
Loading