Skip to content

Commit

Permalink
Support http authorization in asyncapi generation (aklivity#1145)
Browse files Browse the repository at this point in the history
* Adjust padding to accommodate good enough headers and don't include partial data frame while computing crc32c value

* Support http oauth in asyncapi
  • Loading branch information
akrambek authored Jul 13, 2024
1 parent b95b0a9 commit 1a0fd5d
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiParameter;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSchema;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSecurityScheme;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiServer;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiSecuritySchemeView;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView;
import io.aklivity.zilla.runtime.binding.http.config.HttpAuthorizationConfig;
import io.aklivity.zilla.runtime.binding.http.config.HttpConditionConfig;
Expand Down Expand Up @@ -65,7 +67,7 @@ public class AsyncapiHttpProtocol extends AsyncapiProtocol

private static final String SECURE_SCHEME = "https";
private final Map<String, String> securitySchemes;
private final boolean isJwtEnabled;
private final boolean isOauthEnabled;
private final String guardName;
private final HttpAuthorizationConfig authorization;

Expand All @@ -77,7 +79,7 @@ protected AsyncapiHttpProtocol(
{
super(qname, asyncapis, protocol, SCHEME);
this.securitySchemes = resolveSecuritySchemes();
this.isJwtEnabled = !securitySchemes.isEmpty();
this.isOauthEnabled = !securitySchemes.isEmpty();

final HttpOptionsConfig httpOptions = options.http;
this.guardName = httpOptions != null ? String.format("%s:%s", qname, httpOptions.authorization.name) : null;
Expand All @@ -100,7 +102,8 @@ public <C> BindingConfigBuilder<C> injectProtocolServerOptions(

@Override
public <C> BindingConfigBuilder<C> injectProtocolServerRoutes(
BindingConfigBuilder<C> binding)
BindingConfigBuilder<C> binding,
AsyncapiOptionsConfig options)
{
for (Asyncapi asyncapi : asyncapis)
{
Expand All @@ -127,7 +130,8 @@ public <C> BindingConfigBuilder<C> injectProtocolServerRoutes(
.header(":path", path)
.header(":method", method)
.build()
.inject(route -> injectHttpServerRouteGuarded(route, server))
.inject(route -> injectHttpServerRouteGuarded(
route, options.http, asyncapi, operation.security))
.build();
}
}
Expand Down Expand Up @@ -167,9 +171,9 @@ protected boolean isSecure()
private <C> HttpOptionsConfigBuilder<C> injectHttpServerOptions(
HttpOptionsConfigBuilder<C> options)
{
if (isJwtEnabled)
if (isOauthEnabled)
{
options.authorization(authorization).build();
options.authorization(authorization);
}
return options;
}
Expand Down Expand Up @@ -264,24 +268,22 @@ private <C> HttpRequestConfigBuilder<C> injectPathParams(

private <C> RouteConfigBuilder<C> injectHttpServerRouteGuarded(
RouteConfigBuilder<C> route,
AsyncapiServerView server)
HttpOptionsConfig options,
Asyncapi asyncapi,
List<AsyncapiSecurityScheme> securities)
{
if (server.security() != null)
if (securities != null && !securities.isEmpty())
{
for (Map<String, List<String>> securityItem : server.security())
AsyncapiSecuritySchemeView security =
AsyncapiSecuritySchemeView.of(asyncapi.components.securitySchemes, securities.get(0));

if (isOauthEnabled && "oauth2".equals(security.type()))
{
for (String securityItemLabel : securityItem.keySet())
{
if (isJwtEnabled && "jwt".equals(securitySchemes.get(securityItemLabel)))
{
route
.guarded()
.name(guardName)
.inject(guarded -> injectGuardedRoles(guarded, securityItem.get(securityItemLabel)))
.build();
break;
}
}
route
.guarded()
.name(options.authorization.qname)
.inject(guarded -> injectGuardedRoles(guarded, security.scopes()))
.build();
}
}
return route;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ private <C> MqttUserPropertyConfigBuilder<C> injectUserProperty(

@Override
public <C> BindingConfigBuilder<C> injectProtocolServerRoutes(
BindingConfigBuilder<C> binding)
BindingConfigBuilder<C> binding,
AsyncapiOptionsConfig options)
{
for (Asyncapi asyncapi : asyncapis)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ public void init(
this.namespace = binding.namespace;
this.qvault = binding.qvault;
this.vault = binding.vault;

AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options;
if (options.http != null && options.http.authorization != null)
{
options.http.authorization.qname = String.format("%s:%s", namespace, options.http.authorization.name);
}
}

public NamespaceConfig generate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ public abstract <C>BindingConfigBuilder<C> injectProtocolServerOptions(
BindingConfigBuilder<C> binding);

public abstract <C> BindingConfigBuilder<C> injectProtocolServerRoutes(
BindingConfigBuilder<C> binding);
BindingConfigBuilder<C> binding,
AsyncapiOptionsConfig options);

public <C> NamespaceConfigBuilder<C> injectProtocolClientCache(
NamespaceConfigBuilder<C> namespace,
Expand Down Expand Up @@ -219,13 +220,13 @@ protected Map<String, String> resolveSecuritySchemes()
{
for (String securitySchemeName : asyncapi.components.securitySchemes.keySet())
{
String guardType = asyncapi.components.securitySchemes.get(securitySchemeName).bearerFormat;
//TODO: change when jwt support added for mqtt in asyncapi
//String guardType = asyncapi.components.securitySchemes.get(securitySchemeName).bearerFormat;
//if ("jwt".equals(guardType))
//{
// result.put(securitySchemeName, guardType);
//}
result.put(securitySchemeName, guardType);
result.put(securitySchemeName, "");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public NamespaceConfig generate(
.inject(n -> injectTcpServer(n, servers, options, metricRefs))
.inject(n -> injectTlsServer(n, servers, options))
.inject(n -> injectProtocolRelatedBindings(n, servers, metricRefs))
.inject(n -> injectProtocolServers(n, servers, metricRefs))
.inject(n -> injectProtocolServers(n, options, servers, metricRefs))
.build();
}

Expand All @@ -71,6 +71,7 @@ protected <C> NamespaceConfigBuilder<C> injectProtocolRelatedBindings(

private <C> NamespaceConfigBuilder<C> injectProtocolServers(
NamespaceConfigBuilder<C> namespace,
AsyncapiOptionsConfig options,
List<AsyncapiServerView> servers,
List<MetricRefConfig> metricRefs)
{
Expand All @@ -84,7 +85,7 @@ private <C> NamespaceConfigBuilder<C> injectProtocolServers(
.inject(b -> this.injectMetrics(b, metricRefs))
.kind(SERVER)
.inject(protocol::injectProtocolServerOptions)
.inject(protocol::injectProtocolServerRoutes)
.inject(b -> protocol.injectProtocolServerRoutes(b, options))
.build();
}
return namespace;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiChannel;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSecurityScheme;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiSecuritySchemeView;
import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig;
import io.aklivity.zilla.runtime.binding.sse.config.SseConditionConfig;
import io.aklivity.zilla.runtime.binding.sse.config.SseOptionsConfig;
import io.aklivity.zilla.runtime.binding.sse.config.SseOptionsConfigBuilder;
import io.aklivity.zilla.runtime.binding.sse.config.SsePathConfigBuilder;
import io.aklivity.zilla.runtime.common.feature.Incubating;
import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder;
import io.aklivity.zilla.runtime.engine.config.GuardedConfigBuilder;
import io.aklivity.zilla.runtime.engine.config.MetricRefConfig;
import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder;
import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder;
import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig;

@Incubating
Expand Down Expand Up @@ -73,7 +78,7 @@ public <C> NamespaceConfigBuilder<C> injectProtocolRelatedServerBindings(
.inject(b -> this.injectMetrics(b, metricRefs))
.kind(SERVER)
.inject(httpProtocol::injectProtocolServerOptions)
.inject(httpProtocol::injectProtocolServerRoutes)
.inject(b -> httpProtocol.injectProtocolServerRoutes(b, options))
.build();
}
return namespace;
Expand All @@ -92,7 +97,8 @@ public <C> BindingConfigBuilder<C> injectProtocolServerOptions(

@Override
public <C> BindingConfigBuilder<C> injectProtocolServerRoutes(
BindingConfigBuilder<C> binding)
BindingConfigBuilder<C> binding,
AsyncapiOptionsConfig options)
{
for (Asyncapi asyncapi : asyncapis)
{
Expand All @@ -109,6 +115,8 @@ public <C> BindingConfigBuilder<C> injectProtocolServerRoutes(
.when(SseConditionConfig::builder)
.path(path)
.build()
.inject(route -> injectHttpServerRouteGuarded(
route, qname, options.http, asyncapi, operation.security))
.build();
}
}
Expand Down Expand Up @@ -181,4 +189,39 @@ private <C> SsePathConfigBuilder<C> injectValue(
}
return request;
}

private <C> RouteConfigBuilder<C> injectHttpServerRouteGuarded(
RouteConfigBuilder<C> route,
String qname,
HttpOptionsConfig options,
Asyncapi asyncapi,
List<AsyncapiSecurityScheme> securities)
{
if (securities != null && !securities.isEmpty())
{
AsyncapiSecuritySchemeView security =
AsyncapiSecuritySchemeView.of(asyncapi.components.securitySchemes, securities.get(0));

if ("oauth2".equals(security.type()))
{
route
.guarded()
.name(String.format("%s:%s", qname, options.authorization.name))
.inject(guarded -> injectGuardedRoles(guarded, security.scopes()))
.build();
}
}
return route;
}

private <C> GuardedConfigBuilder<C> injectGuardedRoles(
GuardedConfigBuilder<C> guarded,
List<String> roles)
{
for (String role : roles)
{
guarded.role(role);
}
return guarded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ public <C> BindingConfigBuilder<C> injectProtocolServerOptions(

@Override
public <C> BindingConfigBuilder<C> injectProtocolServerRoutes(
BindingConfigBuilder<C> binding)
BindingConfigBuilder<C> binding,
AsyncapiOptionsConfig options)
{
return binding;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/
package io.aklivity.zilla.runtime.binding.asyncapi.internal.model;

import java.util.List;
import java.util.Map;

public class AsyncapiOperation
Expand All @@ -22,4 +23,5 @@ public class AsyncapiOperation
public AsyncapiChannel channel;
public String action;
public AsyncapiReply reply;
public List<AsyncapiSecurityScheme> security;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,20 @@
*/
package io.aklivity.zilla.runtime.binding.asyncapi.internal.model;

import java.util.List;

import jakarta.json.bind.annotation.JsonbProperty;

public class AsyncapiSecurityScheme
{
public String bearerFormat;
public String type;
public String name;
public String in;
public String scheme;
public Object flows;
public String openIdConnectUrl;
public List<String> scopes;

@JsonbProperty("$ref")
public String ref;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@
*/
package io.aklivity.zilla.runtime.binding.asyncapi.internal.model;

import java.util.List;
import java.util.Map;

import jakarta.json.JsonArray;

public class AsyncapiServer
{
public String host;
public String url;
public String pathname;
public String protocol;
public JsonArray security;
public List<AsyncapiSecurityScheme> security;
public Map<String, AsyncapiVariable> variables;
public AsyncapiServerBindings bindings;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2021-2023 Aklivity Inc
*
* Licensed under the Aklivity Community License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at
*
* https://www.aklivity.io/aklivity-community-license/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package io.aklivity.zilla.runtime.binding.asyncapi.internal.view;

import java.util.List;
import java.util.Map;

import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSecurityScheme;

public final class AsyncapiSecuritySchemeView extends AsyncapiResolvable<AsyncapiSecurityScheme>
{
private final AsyncapiSecurityScheme scheme;

public String type()
{
return scheme.type;
}

public List<String> scopes()
{
return scheme.scopes;
}

public String refKey()
{
return key;
}

public static AsyncapiSecuritySchemeView of(
Map<String, AsyncapiSecurityScheme> schemes,
AsyncapiSecurityScheme scheme)
{
return new AsyncapiSecuritySchemeView(schemes, scheme);
}

private AsyncapiSecuritySchemeView(
Map<String, AsyncapiSecurityScheme> schemes,
AsyncapiSecurityScheme scheme)
{
super(schemes, "#/components/securitySchemes/(.+)");
this.scheme = scheme.ref == null ? scheme : resolveRef(scheme.ref);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public final class HttpAuthorizationConfig
public final String name;
public final HttpCredentialsConfig credentials;

public transient String qname;

public static HttpAuthorizationConfigBuilder<HttpAuthorizationConfig> builder()
{
return new HttpAuthorizationConfigBuilder<>(identity());
Expand Down

0 comments on commit 1a0fd5d

Please sign in to comment.