Skip to content
Draft
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 @@ -38,6 +38,21 @@ private LinkedHashMap<String, List<String>> contains(
SecurityScheme leftSecurityScheme = leftComponents.getSecuritySchemes().get(schemeRef);
LinkedHashMap<String, List<String>> found = new LinkedHashMap<>();

// Match by component key first: when the other requirement still references the same
// securityScheme key, it is the same scheme even if the apiKey parameter name changed,
// and the name change is diffed as a scheme change. Matching by name first mispairs
// schemes when a requirement contains several apiKey schemes.
for (Map.Entry<String, List<String>> entry : right.entrySet()) {
SecurityScheme rightSecurityScheme = rightComponents.getSecuritySchemes().get(entry.getKey());
if (schemeRef.equals(entry.getKey())
&& leftSecurityScheme.getType() == rightSecurityScheme.getType()) {
found.put(entry.getKey(), entry.getValue());
return found;
}
}

// Fall back to matching by parameter name so a renamed component key is not reported
// as a removed and re-added scheme while the parameter name clients send is unchanged.
for (Map.Entry<String, List<String>> entry : right.entrySet()) {
SecurityScheme rightSecurityScheme = rightComponents.getSecuritySchemes().get(entry.getKey());
if (leftSecurityScheme.getType() == rightSecurityScheme.getType()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ protected DeferredChanged<ChangedSecurityScheme> computeDiff(
case APIKEY:
changedSecurityScheme.setChangedIn(
!Objects.equals(leftSecurityScheme.getIn(), rightSecurityScheme.getIn()));
// The parameter name is an apiKey-only property; when the scheme is no longer an
// apiKey on the other side the type change is reported instead.
if (rightSecurityScheme.getType() == SecurityScheme.Type.APIKEY) {
changedSecurityScheme.setChangedName(
!Objects.equals(leftSecurityScheme.getName(), rightSecurityScheme.getName()));
}
break;

case OAUTH2:
Expand Down Expand Up @@ -121,6 +127,7 @@ private ChangedSecurityScheme getCopyWithoutScopes(
.setChangedType(original.isChangedType())
.setChangedIn(original.isChangedIn())
.setChangedScheme(original.isChangedScheme())
.setChangedName(original.isChangedName())
.setChangedBearerFormat(original.isChangedBearerFormat())
.setDescription(original.getDescription())
.setOAuthFlows(original.getOAuthFlows())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public enum BackwardIncompatibleProp {
SECURITY_REQUIREMENT_SCHEMES_INCREASED(
"incompatible.security.requirement.schemes.increased", true),
SECURITY_REQUIREMENTS_DECREASED("incompatible.security.requirements.decreased", true),
SECURITY_SCHEME_API_KEY_NAME_CHANGED("incompatible.security.scheme.apikey.name.changed", true),
SECURITY_SCHEME_BEARER_FORMAT_CHANGED("incompatible.security.scheme.bearer.format.changed", true),
SECURITY_SCHEME_OAUTH2_AUTH_URL_CHANGED(
"incompatible.security.scheme.oauth2.auth.url.changed", true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class ChangedSecurityScheme implements ComposedChanged {
private boolean changedType;
private boolean changedIn;
private boolean changedScheme;
private boolean changedName;
private boolean changedBearerFormat;
private boolean changedOpenIdConnectUrl;
private ChangedSecuritySchemeScopes changedScopes;
Expand All @@ -38,12 +39,18 @@ public DiffResult isCoreChanged() {
if (!changedType
&& !changedIn
&& !changedScheme
&& !changedName
&& !changedBearerFormat
&& !changedOpenIdConnectUrl
&& (changedScopes == null || changedScopes.isUnchanged())) {
return DiffResult.NO_CHANGES;
}

if (changedName) {
if (SECURITY_SCHEME_API_KEY_NAME_CHANGED.enabled(context)) {
return DiffResult.INCOMPATIBLE;
}
}
if (changedBearerFormat) {
if (SECURITY_SCHEME_BEARER_FORMAT_CHANGED.enabled(context)) {
return DiffResult.INCOMPATIBLE;
Expand Down Expand Up @@ -95,6 +102,10 @@ public boolean isChangedScheme() {
return this.changedScheme;
}

public boolean isChangedName() {
return this.changedName;
}

public boolean isChangedBearerFormat() {
return this.changedBearerFormat;
}
Expand Down Expand Up @@ -144,6 +155,11 @@ public ChangedSecurityScheme setChangedScheme(final boolean changedScheme) {
return this;
}

public ChangedSecurityScheme setChangedName(final boolean changedName) {
this.changedName = changedName;
return this;
}

public ChangedSecurityScheme setChangedBearerFormat(final boolean changedBearerFormat) {
this.changedBearerFormat = changedBearerFormat;
return this;
Expand Down Expand Up @@ -182,6 +198,7 @@ public boolean equals(Object o) {
return changedType == that.changedType
&& changedIn == that.changedIn
&& changedScheme == that.changedScheme
&& changedName == that.changedName
&& changedBearerFormat == that.changedBearerFormat
&& changedOpenIdConnectUrl == that.changedOpenIdConnectUrl
&& Objects.equals(oldSecurityScheme, that.oldSecurityScheme)
Expand All @@ -200,6 +217,7 @@ public int hashCode() {
changedType,
changedIn,
changedScheme,
changedName,
changedBearerFormat,
changedOpenIdConnectUrl,
changedScopes,
Expand All @@ -220,6 +238,8 @@ public java.lang.String toString() {
+ this.isChangedIn()
+ ", changedScheme="
+ this.isChangedScheme()
+ ", changedName="
+ this.isChangedName()
+ ", changedBearerFormat="
+ this.isChangedBearerFormat()
+ ", changedOpenIdConnectUrl="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.openapidiff.core.exception.RendererException;
import org.openapitools.openapidiff.core.model.*;
Expand Down Expand Up @@ -104,7 +107,189 @@ private void ol_changed(
safelyAppend(outputStreamWriter, System.lineSeparator());
safelyAppend(outputStreamWriter, ul_response(operation.getApiResponses()));
}
if (operation.resultSecurityRequirements().isDifferent()) {
safelyAppend(outputStreamWriter, StringUtils.repeat(' ', 2));
safelyAppend(outputStreamWriter, "Security Requirements:");
safelyAppend(outputStreamWriter, System.lineSeparator());
safelyAppend(
outputStreamWriter, ul_securityRequirements(operation.getSecurityRequirements()));
}
}
}

private String ul_securityRequirements(ChangedSecurityRequirements changedSecurityRequirements) {
StringBuilder sb = new StringBuilder();
if (changedSecurityRequirements.getIncreased() != null) {
for (SecurityRequirement requirement : changedSecurityRequirements.getIncreased()) {
sb.append(itemSecurityRequirement("New security requirement", requirement));
}
}
if (changedSecurityRequirements.getMissing() != null) {
for (SecurityRequirement requirement : changedSecurityRequirements.getMissing()) {
sb.append(itemSecurityRequirement("Deleted security requirement", requirement));
}
}
for (ChangedSecurityRequirement requirement : changedSecurityRequirements.getChanged()) {
sb.append(itemChangedSecurityRequirement(requirement));
}
return sb.toString();
}

private String itemSecurityRequirement(String title, SecurityRequirement securityRequirement) {
return String.format(
"%s- %s: %s%n",
StringUtils.repeat(' ', 4), title, securityRequirementWithScopes(securityRequirement));
}

private String itemChangedSecurityRequirement(
ChangedSecurityRequirement changedSecurityRequirement) {
StringBuilder sb = new StringBuilder();
SecurityRequirement nameSource = changedSecurityRequirement.getNewSecurityRequirement();
if (nameSource == null || nameSource.isEmpty()) {
nameSource = changedSecurityRequirement.getOldSecurityRequirement();
}
sb.append(
String.format(
"%s- Changed security requirement: %s%n",
StringUtils.repeat(' ', 4), securityRequirementName(nameSource)));
if (changedSecurityRequirement.getIncreased() != null) {
sb.append(
itemSecuritySchemeChange(
"New security scheme",
securityRequirementWithScopes(changedSecurityRequirement.getIncreased())));
}
if (changedSecurityRequirement.getMissing() != null) {
sb.append(
itemSecuritySchemeChange(
"Deleted security scheme",
securityRequirementWithScopes(changedSecurityRequirement.getMissing())));
}
for (ChangedSecurityScheme scheme : changedSecurityRequirement.getChanged()) {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
sb.append(itemChangedSecurityScheme(scheme));
}
return sb.toString();
}

private String itemChangedSecurityScheme(ChangedSecurityScheme changedSecurityScheme) {
SecurityScheme oldScheme = changedSecurityScheme.getOldSecurityScheme();
SecurityScheme newScheme = changedSecurityScheme.getNewSecurityScheme();
StringBuilder sb = new StringBuilder();
if (changedSecurityScheme.isChangedType()) {
sb.append(
itemSecuritySchemeChange(
"Changed security scheme type",
String.format("%s -> %s", oldScheme.getType(), newScheme.getType())));
}
if (changedSecurityScheme.isChangedIn()) {
sb.append(
itemSecuritySchemeChange(
"Changed security scheme location",
String.format("%s -> %s", oldScheme.getIn(), newScheme.getIn())));
}
if (changedSecurityScheme.isChangedName()) {
sb.append(
itemSecuritySchemeChange(
"Changed security scheme name",
String.format("%s -> %s", oldScheme.getName(), newScheme.getName())));
}
if (changedSecurityScheme.isChangedScheme()) {
sb.append(
itemSecuritySchemeChange(
"Changed security scheme",
String.format("%s -> %s", oldScheme.getScheme(), newScheme.getScheme())));
}
if (changedSecurityScheme.isChangedBearerFormat()) {
sb.append(
itemSecuritySchemeChange(
"Changed bearer format",
String.format("%s -> %s", oldScheme.getBearerFormat(), newScheme.getBearerFormat())));
}
if (changedSecurityScheme.isChangedOpenIdConnectUrl()) {
sb.append(
itemSecuritySchemeChange(
"Changed openIdConnectUrl",
String.format(
"%s -> %s", oldScheme.getOpenIdConnectUrl(), newScheme.getOpenIdConnectUrl())));
}
if (changedSecurityScheme.getChangedScopes() != null) {
ChangedSecuritySchemeScopes changedScopes = changedSecurityScheme.getChangedScopes();
if (!changedScopes.getIncreased().isEmpty()) {
sb.append(
itemSecuritySchemeChange(
"Added scope value(s)", String.join(", ", changedScopes.getIncreased())));
}
if (!changedScopes.getMissing().isEmpty()) {
sb.append(
itemSecuritySchemeChange(
"Removed scope value(s)", String.join(", ", changedScopes.getMissing())));
}
}
if (changedSecurityScheme.getOAuthFlows() != null) {
ChangedOAuthFlows changedOAuthFlows = changedSecurityScheme.getOAuthFlows();
sb.append(itemChangedOAuthFlow("implicit", changedOAuthFlows.getImplicitOAuthFlow()));
sb.append(itemChangedOAuthFlow("password", changedOAuthFlows.getPasswordOAuthFlow()));
sb.append(
itemChangedOAuthFlow(
"clientCredentials", changedOAuthFlows.getClientCredentialOAuthFlow()));
sb.append(
itemChangedOAuthFlow(
"authorizationCode", changedOAuthFlows.getAuthorizationCodeOAuthFlow()));
}
return sb.toString();
}

private String itemChangedOAuthFlow(String flowName, ChangedOAuthFlow changedOAuthFlow) {
if (changedOAuthFlow == null) {
return "";
}
StringBuilder sb = new StringBuilder();
if (changedOAuthFlow.isAuthorizationUrl()) {
sb.append(
itemSecuritySchemeChange(
String.format("Changed %s flow authorizationUrl", flowName),
String.format(
"%s -> %s",
changedOAuthFlow.getOldOAuthFlow().getAuthorizationUrl(),
changedOAuthFlow.getNewOAuthFlow().getAuthorizationUrl())));
}
if (changedOAuthFlow.isTokenUrl()) {
sb.append(
itemSecuritySchemeChange(
String.format("Changed %s flow tokenUrl", flowName),
String.format(
"%s -> %s",
changedOAuthFlow.getOldOAuthFlow().getTokenUrl(),
changedOAuthFlow.getNewOAuthFlow().getTokenUrl())));
}
if (changedOAuthFlow.isRefreshUrl()) {
sb.append(
itemSecuritySchemeChange(
String.format("Changed %s flow refreshUrl", flowName),
String.format(
"%s -> %s",
changedOAuthFlow.getOldOAuthFlow().getRefreshUrl(),
changedOAuthFlow.getNewOAuthFlow().getRefreshUrl())));
}
return sb.toString();
}

private String itemSecuritySchemeChange(String title, String detail) {
return String.format(
"%s* %s: %s%n", StringUtils.repeat(' ', 6), title, StringUtils.defaultString(detail));
}

private String securityRequirementWithScopes(SecurityRequirement securityRequirement) {
if (securityRequirement == null) {
return "";
}
return securityRequirement.entrySet().stream()
.map(
entry -> String.format("%s: [%s]", entry.getKey(), String.join(", ", entry.getValue())))
.collect(Collectors.joining(", "));
}

private String securityRequirementName(SecurityRequirement securityRequirement) {
return securityRequirement == null ? "" : String.join(", ", securityRequirement.keySet());
}

private String ul_response(ChangedApiResponse changedApiResponse) {
Expand Down
Loading