Skip to content

Commit

Permalink
Merge pull request #1807 from apache/CAUSEWAY-3545
Browse files Browse the repository at this point in the history
CAUSEWAY-3545: adds in Jwt classes, refactors RestfulClient for Oauth…
  • Loading branch information
danhaywood committed Jul 30, 2023
2 parents 15612ee + 61c5d82 commit 89c94b0
Show file tree
Hide file tree
Showing 29 changed files with 854 additions and 257 deletions.
11 changes: 9 additions & 2 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ It is therefore a copy of org.apache:apache, with customisations clearly identif

<htmlparser.version>2.1</htmlparser.version>

<failsafe.version>2.4.4</failsafe.version>

<javafaker.version>1.0.2</javafaker.version>
<javassist.version>3.29.2-GA</javassist.version>

Expand Down Expand Up @@ -1444,7 +1446,12 @@ It is therefore a copy of org.apache:apache, with customisations clearly identif
<artifactId>woodstox-core</artifactId>
<version>6.5.1</version>
</dependency>
<dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>failsafe</artifactId>
<version>${failsafe.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>stax2-api</artifactId>
<version>4.2.1</version>
Expand Down Expand Up @@ -1486,7 +1493,7 @@ It is therefore a copy of org.apache:apache, with customisations clearly identif
<version>${asm.version}</version>
</dependency>

<dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2009,7 +2009,7 @@ public static class Authentication {
/**
* Defaults to <code>org.apache.causeway.viewer.restfulobjects.viewer.webmodule.auth.AuthenticationStrategyBasicAuth</code>.
*/
private Optional<String> strategyClassName = Optional.empty();
private String strategyClassName = "org.apache.causeway.viewer.restfulobjects.viewer.webmodule.auth.AuthenticationStrategyBasicAuth";
}

/**
Expand Down Expand Up @@ -2306,12 +2306,12 @@ public void setMaxTitleLengthInStandaloneTables(final int val) {
}

/**
* If a table has no property columns,
* If a table has no property columns,
* for the title column this value is used,
* to determine how many characters to render for the table element titles.
* <p>
* Introduced for the case when max-title-length is set to zero for tables in general,
* that if a table has no property columns an exception to that title suppression can be made.
* Introduced for the case when max-title-length is set to zero for tables in general,
* that if a table has no property columns an exception to that title suppression can be made.
*/
private int maxTitleLengthInTablesNotHavingAnyPropertyColumn = 80;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,19 +157,19 @@ private String getUnusedRandomCode() {


// cannot use final here, as Spring provides a transaction aware proxy for this type
public /*final*/ boolean isSessionValid(final @Nullable InteractionContext authentication) {
if(authentication==null) {
public /*final*/ boolean isSessionValid(final @Nullable InteractionContext interactionContext) {
if(interactionContext==null) {
return false;
}
val userMemento = authentication.getUser();
val userMemento = interactionContext.getUser();
if(userMemento.getAuthenticationSource().isExternal()) {
return true;
}
if(userMemento.isImpersonating()) {
return true;
}
final String userName = userByValidationCode.get(userMemento.getAuthenticationCode());
return authentication.getUser().isCurrentUser(userName);
return interactionContext.getUser().isCurrentUser(userName);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
import java.util.List;

import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.core.MediaType;

import org.apache.causeway.applib.util.schema.InteractionsDtoUtils;
import org.apache.causeway.commons.functional.Try;
import org.apache.causeway.commons.io.JsonUtils;
import org.apache.causeway.extensions.executionoutbox.restclient.api.delete.DeleteMessage;
import org.apache.causeway.extensions.executionoutbox.restclient.api.deleteMany.DeleteManyMessage;
import org.apache.causeway.viewer.restfulobjects.client.AuthenticationMode;
import org.apache.causeway.schema.common.v2.InteractionType;
import org.apache.causeway.schema.ixn.v2.ActionInvocationDto;
import org.apache.causeway.schema.ixn.v2.InteractionDto;
Expand All @@ -40,8 +42,8 @@
import org.apache.causeway.viewer.restfulobjects.client.RestfulClient;
import org.apache.causeway.viewer.restfulobjects.client.RestfulClientConfig;
import org.apache.causeway.viewer.restfulobjects.client.RestfulClientMediaType;
import org.apache.causeway.viewer.restfulobjects.client.auth.AuthorizationHeaderFactory;

import lombok.Setter;
import lombok.val;
import lombok.extern.log4j.Log4j2;

Expand All @@ -51,94 +53,86 @@
@Log4j2
public class OutboxClient {

private final RestfulClientConfig restfulClientConfig;

public OutboxClient() {
this.restfulClientConfig = new RestfulClientConfig();
private final RestfulClient client;
private final OutboxClientConfig outboxClientConfig;

public OutboxClient(
final String restfulBaseUrl,
final String username,
final String password) {
this(RestfulClientConfig.builder()
.restfulBaseUrl(restfulBaseUrl)
.authenticationMode(AuthenticationMode.BASIC)
.basicAuthUser(username)
.basicAuthPassword(password)
.build()
);
}

/**
* Will automatically call {@link #init()} since all properties already supplied.
*/
public OutboxClient(final String base, final String username, final String password) {
this();
public OutboxClient(
final String restfulBaseUrl,
final String tenantId,
final String clientId,
final String clientSecret) {
this(RestfulClientConfig.builder()
.restfulBaseUrl(restfulBaseUrl)
.authenticationMode(AuthenticationMode.OAUTH2_AZURE)
.oauthTenantId(tenantId)
.oauthClientId(clientId)
.oauthClientSecret(clientSecret)
.build()
);
}

setBase(base);
setUsername(username);
setPassword(password);
public OutboxClient(final RestfulClientConfig restfulClientConfig) {
this(restfulClientConfig, new OutboxClientConfig());
}

init();
public OutboxClient(final RestfulClientConfig restfulClientConfig, final OutboxClientConfig outboxClientConfig) {
this.client = RestfulClient.ofConfig(restfulClientConfig);
this.outboxClientConfig = outboxClientConfig;
}

/**
* for debugging
* @param connectTimeoutInSecs
*/
public OutboxClient withConnectTimeoutInSecs(final int connectTimeoutInSecs) {
setConnectTimeoutInSecs(connectTimeoutInSecs);
return this;
public OutboxClient(
final RestfulClientConfig restfulClientConfig,
final AuthorizationHeaderFactory authorizationHeaderFactory) {
this(restfulClientConfig, authorizationHeaderFactory, new OutboxClientConfig());
}

/**
* for debugging
* @param readTimeoutInSecs
*/
public OutboxClient withReadTimeoutInSecs(final int readTimeoutInSecs) {
setReadTimeoutInSecs(readTimeoutInSecs);
return this;
public OutboxClient(
final RestfulClientConfig restfulClientConfig,
final AuthorizationHeaderFactory authorizationHeaderFactory,
final OutboxClientConfig outboxClientConfig) {
this.outboxClientConfig = outboxClientConfig;
this.client = RestfulClient.ofConfig(restfulClientConfig, authorizationHeaderFactory);
}

@Setter private String base;
@Setter private String username;
@Setter private String password;
@Setter private int connectTimeoutInSecs;
@Setter private int readTimeoutInSecs;

/**
* Should be called once all properties have been injected.
*/
public void init() {
restfulClientConfig.setRestfulBase(base);
restfulClientConfig.setUseBasicAuth(true);
restfulClientConfig.setRestfulAuthUser(username);
restfulClientConfig.setRestfulAuthPassword(password);
restfulClientConfig.setConnectTimeoutInMillis(1000L * connectTimeoutInSecs);
restfulClientConfig.setReadTimeoutInMillis(1000L * readTimeoutInSecs);
//restfulClientConfig.setUseRequestDebugLogging(true); //for debugging
public OutboxClient withConnectTimeoutInSecs(int connectTimeoutInSecs) {
client.getConfig().setConnectTimeoutInMillis(1000L * connectTimeoutInSecs);
return this;
}

private void ensureInitialized() {
if(username == null || password == null || base == null) {
throw new IllegalStateException("Must initialize 'username', 'password' and 'base' properties");
}
public OutboxClient withReadTimeoutInSecs(int readTimeoutInSecs) {
client.getConfig().setReadTimeoutInMillis(1000L * readTimeoutInSecs);
return this;
}

public List<InteractionDto> pending() {

ensureInitialized();
Invocation.Builder invocationBuilder = client.request(outboxClientConfig.getPendingUri())
.accept(RestfulClientMediaType.RO_XML.mediaTypeFor(InteractionsDto.class));
var response = invocationBuilder.get();

try(val client = RestfulClient.ofConfig(restfulClientConfig)) {

var response = client.request(PENDING_URI)
.accept(RestfulClientMediaType.RO_XML.mediaTypeFor(InteractionsDto.class))
.get();

final Try<InteractionsDto> digest = client.digest(response, InteractionsDto.class);

if(digest.isSuccess()) {
return digest.getValue()
.map(InteractionsDto::getInteractionDto)
.orElseGet(Collections::emptyList);
} else {
log.error("Failed to GET from {}: {}", client.uri(PENDING_URI), digest.getFailure().get());
return Collections.emptyList();
}
}
final Try<InteractionsDto> digest = client.digest(response, InteractionsDto.class);

digest.ifFailureFail();
return digest.getValue()
.map(InteractionsDto::getInteractionDto)
.orElseGet(Collections::emptyList);
}

public void delete(final String interactionId, final int sequence) {
invoke(DELETE_URI,
invoke(outboxClientConfig.getDeleteUri(),
new DeleteMessage(interactionId, sequence));
}

Expand All @@ -147,16 +141,12 @@ public void deleteMany(final List<InteractionDto> interactionDtos) {
interactionDtos.forEach(interactionDto -> {
addTo(interactionsDto, interactionDto);
});
invoke(DELETE_MANY_URI,
invoke(outboxClientConfig.getDeleteManyUri(),
new DeleteManyMessage(InteractionsDtoUtils.dtoMapper().toString(interactionsDto)));
}

// -- HELPER

private static String PENDING_URI = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/pending/invoke";
private static String DELETE_URI = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/delete/invoke";
private static String DELETE_MANY_URI = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/deleteMany/invoke";

private void addTo(final InteractionsDto interactionsDto, final InteractionDto orig) {
val copy = new InteractionDto();
copy.setInteractionId(orig.getInteractionId());
Expand All @@ -179,24 +169,18 @@ private MemberExecutionDto newMemberExecutionDto(final InteractionDto orig) {

private void invoke(final String path, final Object dto) {

ensureInitialized();

try(val client = RestfulClient.ofConfig(restfulClientConfig)) {
val invocationBuilder = client.request(path);

var invocationBuilder = client.request(path);
val invocation = invocationBuilder.buildPut(
Entity.entity(JsonUtils.toStringUtf8(dto), MediaType.APPLICATION_JSON_TYPE));

val invocation = invocationBuilder.buildPut(
Entity.entity(JsonUtils.toStringUtf8(dto), MediaType.APPLICATION_JSON_TYPE));
val response = invocation.invoke();

val response = invocation.invoke();

val responseStatus = response.getStatus();
if (responseStatus != 200) {
// if failed to log message via REST service, then fallback by logging to slf4j
log.warn(dto.toString());
}
val responseStatus = response.getStatus();
if (responseStatus != 200) {
// if failed to log message via REST service, then fallback by logging to slf4j
log.warn(dto.toString());
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.causeway.extensions.executionoutbox.restclient.api;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.ws.rs.ProcessingException;
import javax.xml.bind.annotation.*;

import org.apache.causeway.viewer.restfulobjects.client.AuthenticationMode;
import org.apache.causeway.viewer.restfulobjects.client.log.ClientConversationFilter;

/**
* @since 2.0 {@index}
*/
@XmlRootElement(name="outbox-client-config")
@XmlAccessorType(XmlAccessType.FIELD)
@Data @Builder
@AllArgsConstructor
@NoArgsConstructor
public class OutboxClientConfig {

@XmlElement(name="pendingUri")
private String pendingUri = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/pending/invoke";

@XmlElement(name="deleteUri")
private String deleteUri = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/delete/invoke";

@XmlElement(name="deleteManyUri")
private String deleteManyUri = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/deleteMany/invoke";

}
Loading

0 comments on commit 89c94b0

Please sign in to comment.