Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ public void serialize(Boolean value, JsonGenerator gen, SerializerProvider seria
protected boolean usePythonSrcRootInImports = Boolean.FALSE;
protected boolean moveTestsUnderPythonSrcRoot = Boolean.FALSE;

// Python function name suffix per security scheme name (e.g. "api-key" -> "api_key").
// Suffixes are assigned by addSecurityExtensions (called from preprocessOpenAPI):
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
// scheme names that are already valid identifier parts keep their name, the rest are
// numbered in spec order, so that the x-*Func references written into the spec and the
// function definitions emitted by security_controller.mustache always agree, even when
// the scheme name is not a valid Python identifier. fromSecurity only reads the values
// that addSecurityExtensions has already computed.
private final Map<String, String> securityFunctionNameSuffixes = new HashMap<>();

public AbstractPythonConnexionServerCodegen(String templateDirectory, boolean fixBodyNameValue) {
super();

Expand Down Expand Up @@ -459,26 +468,38 @@ private void addSecurityExtensions(OpenAPI openAPI) {
Components components = openAPI.getComponents();
if (components != null && components.getSecuritySchemes() != null) {
Map<String, SecurityScheme> securitySchemes = components.getSecuritySchemes();
securityFunctionNameSuffixes.clear();
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
// Reserve the suffix of names that already are valid identifier parts (no
// character replacement needed) so they keep the same function name as before.
// The remaining names are numbered afterwards, in spec order, avoiding the
// reserved suffixes (e.g. "api-key" listed before "api_key" gets "api_key_2",
// while "api_key" keeps "api_key").
for (String securityName : securitySchemes.keySet()) {
if (toPythonIdentifierSuffix(securityName).equals(securityName)) {
securityFunctionNameSuffixes.put(securityName, securityName);
}
}
for (Map.Entry<String, SecurityScheme> securitySchemesEntry : securitySchemes.entrySet()) {
String securityName = securitySchemesEntry.getKey();
SecurityScheme securityScheme = securitySchemesEntry.getValue();
String functionSuffix = securityFunctionNameSuffix(securityName);
String baseFunctionName = controllerPackage + ".security_controller.";
switch (securityScheme.getType()) {
case APIKEY:
addSecurityExtension(securityScheme, "x-apikeyInfoFunc", baseFunctionName + "info_from_" + securityName);
addSecurityExtension(securityScheme, "x-apikeyInfoFunc", baseFunctionName + "info_from_" + functionSuffix);
break;
case HTTP:
if ("basic".equals(securityScheme.getScheme())) {
addSecurityExtension(securityScheme, "x-basicInfoFunc", baseFunctionName + "info_from_" + securityName);
addSecurityExtension(securityScheme, "x-basicInfoFunc", baseFunctionName + "info_from_" + functionSuffix);
} else if ("bearer".equals(securityScheme.getScheme())) {
addSecurityExtension(securityScheme, "x-bearerInfoFunc", baseFunctionName + "info_from_" + securityName);
addSecurityExtension(securityScheme, "x-bearerInfoFunc", baseFunctionName + "info_from_" + functionSuffix);
}
break;
case OPENIDCONNECT:
LOGGER.warn("Security type {} is not supported by connexion yet", securityScheme.getType().toString());
case OAUTH2:
addSecurityExtension(securityScheme, "x-tokenInfoFunc", baseFunctionName + "info_from_" + securityName);
addSecurityExtension(securityScheme, "x-scopeValidateFunc", baseFunctionName + "validate_scope_" + securityName);
addSecurityExtension(securityScheme, "x-tokenInfoFunc", baseFunctionName + "info_from_" + functionSuffix);
addSecurityExtension(securityScheme, "x-scopeValidateFunc", baseFunctionName + "validate_scope_" + functionSuffix);
break;
default:
LOGGER.warn("Unknown security type {}", securityScheme.getType().toString());
Expand All @@ -487,6 +508,52 @@ private void addSecurityExtensions(OpenAPI openAPI) {
}
}

@Override
public List<CodegenSecurity> fromSecurity(Map<String, SecurityScheme> schemes) {
List<CodegenSecurity> codegenSecurities = super.fromSecurity(schemes);
for (CodegenSecurity codegenSecurity : codegenSecurities) {
codegenSecurity.vendorExtensions.put("x-python-connexion-security-function-suffix",
securityFunctionNameSuffix(codegenSecurity.name));
}
return codegenSecurities;
}

/**
* Returns the Python identifier suffix used for the info_from_/validate_scope_ functions
* of the given security scheme name. The result is stable per scheme name and unique across
* schemes of the same spec. Scheme names that already are valid identifier parts are
* reserved by addSecurityExtensions and keep their name; for the others, if the sanitized
* suffix is already taken by another scheme name (e.g. "api-key" vs "api.key"), "_2",
* "_3", ... is appended in spec order.
*/
private String securityFunctionNameSuffix(String schemeName) {
return securityFunctionNameSuffixes.computeIfAbsent(schemeName, name -> {
String base = toPythonIdentifierSuffix(name);
String suffix = base;
for (int i = 2; securityFunctionNameSuffixes.containsValue(suffix); i++) {
suffix = base + "_" + i;
}
return suffix;
});
}

/**
* Maps a security scheme name to a string usable as part of a Python identifier:
* every character that is not ASCII alphanumeric or '_' becomes '_'. The result is
* only used after the "info_from_"/"validate_scope_" prefixes, so a leading digit
* needs no further fix-up.
*/
private static String toPythonIdentifierSuffix(String name) {
StringBuilder suffix = new StringBuilder(name.length());
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
boolean isIdentifierChar = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9') || c == '_';
suffix.append(isIdentifierChar ? c : '_');
}
return suffix.toString();
}

private static List<OperationMap> getOperations(Map<String, Object> objs) {
List<OperationMap> result = new ArrayList<>();
ApiInfoMap apiInfo = (ApiInfoMap) objs.get("apiInfo");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from typing import List
{{#authMethods}}
{{#isOAuth}}

def info_from_{{name}}(token: str) -> dict:
def info_from_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(token: str) -> dict:
"""
Validate and decode token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
Expand All @@ -14,14 +14,14 @@ def info_from_{{name}}(token: str) -> dict:
return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'}


def validate_scope_{{name}}(required_scopes: List[str], token_scopes: List[str]) -> bool:
def validate_scope_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(required_scopes: List[str], token_scopes: List[str]) -> bool:
""" Validate required scopes are included in token scope """
return set(required_scopes).issubset(set(token_scopes))

{{/isOAuth}}
{{#isApiKey}}

def info_from_{{name}}(api_key: str, required_scopes: None) -> dict:
def info_from_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(api_key: str, required_scopes: None) -> dict:
"""
Check and retrieve authentication information from api_key.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
Expand All @@ -33,7 +33,7 @@ def info_from_{{name}}(api_key: str, required_scopes: None) -> dict:
{{/isApiKey}}
{{#isBasicBasic}}

def info_from_{{name}}(username: str, password: str, required_scopes: None) -> dict:
def info_from_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(username: str, password: str, required_scopes: None) -> dict:
"""
Check and retrieve authentication information from basic auth.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
Expand All @@ -45,7 +45,7 @@ def info_from_{{name}}(username: str, password: str, required_scopes: None) -> d
{{/isBasicBasic}}
{{#isBasicBearer}}

def info_from_{{name}}(token: str) -> dict:
def info_from_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(token: str) -> dict:
"""
Check and retrieve authentication information from custom bearer token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from typing import List
{{#authMethods}}
{{#isOAuth}}

def info_from_{{name}}(token):
def info_from_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(token):
"""
Validate and decode token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
Expand All @@ -18,7 +18,7 @@ def info_from_{{name}}(token):
return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'}


def validate_scope_{{name}}(required_scopes, token_scopes):
def validate_scope_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(required_scopes, token_scopes):
"""
Validate required scopes are included in token scope

Expand All @@ -34,7 +34,7 @@ def validate_scope_{{name}}(required_scopes, token_scopes):
{{/isOAuth}}
{{#isApiKey}}

def info_from_{{name}}(api_key, required_scopes):
def info_from_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(api_key, required_scopes):
"""
Check and retrieve authentication information from api_key.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
Expand All @@ -52,7 +52,7 @@ def info_from_{{name}}(api_key, required_scopes):
{{/isApiKey}}
{{#isBasicBasic}}

def info_from_{{name}}(username, password, required_scopes):
def info_from_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(username, password, required_scopes):
"""
Check and retrieve authentication information from basic auth.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
Expand All @@ -72,7 +72,7 @@ def info_from_{{name}}(username, password, required_scopes):
{{/isBasicBasic}}
{{#isBasicBearer}}

def info_from_{{name}}(token):
def info_from_{{{vendorExtensions.x-python-connexion-security-function-suffix}}}(token):
"""
Check and retrieve authentication information from custom bearer token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
Expand Down
Loading
Loading