Skip to content

Commit

Permalink
Merge branch 'development' into feature/CONNECTOR-136/up-download-end…
Browse files Browse the repository at this point in the history
…points
  • Loading branch information
WilcoLouwerse committed Nov 21, 2024
2 parents 1494be2 + f2e2d5c commit c9efb3f
Show file tree
Hide file tree
Showing 26 changed files with 1,739 additions and 196 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/pull-request-from-branch-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Main Branch Protection

on:
pull_request:
branches:
- main

jobs:
check-branch:
runs-on: ubuntu-latest
steps:
- name: Check branch
run: |
if [[ ${GITHUB_HEAD_REF} != development ]] && [[ ${GITHUB_HEAD_REF} != documentation ]] && ! [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]];
then
echo "Error: Pull request must come from 'development', 'documentation' or 'hotfix/' branch"
exit 1
fi
21 changes: 21 additions & 0 deletions .github/workflows/pull-request-lint-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Lint Check

on:
pull_request:
branches:
- development
- main

jobs:
lint-check:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Install dependencies
run: npm i

- name: Linting
run: npm run lint
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# OpenConector

Provides gateway and service bus functionality like mapping, translation and synchronisation of data

2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The OpenConnector Nextcloud app provides a ESB-framework to work together in an
- 🆓 Map and translate API calls
]]></description>
<version>0.1.14</version>
<version>0.1.16</version>
<licence>agpl</licence>
<category>integration</category>
<author mail="[email protected]" homepage="https://www.conduction.nl/">Conduction</author>
Expand Down
88 changes: 88 additions & 0 deletions docs/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Authentication on sources

In order to authenticate on other sources there are possibilities based upon the way the source expects autentication parameters.
These parameters can be set in the source configuration. For example, if the source expects an API key in the headers, we can set the parameter `headers.Authorization` with the API key as value.

Usually, sources tend to use dynamic authorization parameters in order to prevent the same authentication parameter from being used by adversaries that catch a call and deduce the parameter.

At the moment, OpenConnector supports two dynamic authentication methods, OAuth and JWT Bearers.

## OAuth

To use OAuth we put in our Authorization header the following value:
```twig
Bearer {{ oauthToken(source) }}
```
This will impose an OAuth 2.0 access token after `Bearer` if the field `authenticationConfig` contains correct values.
OpenConnector supports the OAuth 2.0 protocol with client credentials and password credentials as grant_types.

>[!NOTE]
> TODO: How to add authenticationConfig parameters in frontend
When using OAuth, OpenConnector supports the following parameters:

### Standard parameters
* `grant_type`: The type of grant we have to use at the source. Supported are `client_credentials` and `password`
* `scope`: The scope(s) needed to perform the requests we want to do in the API.
* `tokenUrl`: The URL used to fetch the actual access token. Usually this url can be recognised by its path ending on `/oauth/token`
* `authentication`: Location of the credentials, either `body` for credentials included in the request body, or `basic_auth` when the credentials have to be sent as a basic_auth header.
> Only used when `grant_type` is `client_credentials`
* `client_id`: The client id of the OAuth client
> Only used when `grant_type` is `client_credentials`
* `client_secret`: The secret for the OAuth client
> Only used when `grant_type` is `client_credentials`
* `username`: The username for the OAuth client
> Only used when `grant_type` is `password`
* `password`: The password for the OAuth client
> Only used when `grant_type` is `password`
This results in the following example:
```json
{
"grant_type": "client_credentials",
"scope": "api",
"authentication": "body",
"tokenUrl": "https://example.com/oauth/token",
"client_id": "test-client",
"client_secret": "some secret value"
}
```
### Custom parameters

> [!WARNING]
> Custom parameters are currently in beta, it is not recommended to use them in production environments.
At the moment, OpenConnector is tested with the following custom parameters:

* `client_assertion_type`, only meaningful at the moment when value is set to `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`. When this is set (for Microsoft authentications) the following fields are needed to generate the `client-assertion`-field
- `private_key`: The base64 encoded private key of the certificate uploaded to Microsoft.
- `x5t`: The base64 encoded sha1 fingerprint of the uploaded certificate, generated by running the following command:

```bash
echo $(openssl x509 -in certificate.crt -fingerprint -noout) | sed 's/SHA1 Fingerprint=//g' | sed 's/://g' | xxd -r -ps | base64`)
```

- `payload`: The payload of the JWT generated as `client_assertion`, this can contain Twig variables to render, for example to set timestamps in the JWT payload.

## JWT Bearer

A second supported way of using dynamic authentication is setting a JWT Bearer. This means setting a header or query parameter with a JWT token.

This can for example be used by setting an Authorization header with the following value:
```twig
Bearer {{ jwtToken(source) }}
```

This will impose a JWT token after the bearer. For this, the `authenticationConfig` field of the source needs to contain the following fields:
* `algorithm`: The algorithm that should be used to generate the JWT. Supported are `HS256`, `HS384` and `HS512` for HMAC algorithms, `RS256`, `RS384`, `RS512` and `PS256` for RSA algorithms.
* `secret`: The secret used for the JWT. This can either be a HMAC shared secret, or a RSA private key in base64 encoding.
* `payload`: The payload of your JWT, json_encoded.

This results in the following example for the `authenticationConfig` parameter in i.e. an OpenZaak source.
```json
{
"algorithm": "HS256",
"secret": "YOUR_256BIT_(32BYTE)_HMAC_SECRET",
"payload": "{\"iss\":\"my_zgw_client\",\"iat\":{{ 'now'|date('U') }},\"client_id\":\"my_zgw_client\",\"user_id\":\"my_zgw_client\",\"user_representation\":\"[email protected]\",\"aud\":\"my_zgw_client\"}"
}
```
1 change: 1 addition & 0 deletions lib/Service/AuthenticationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ public function fetchJWTToken (array $configuration): string
}

$payload = $this->getJWTPayload($configuration);

$jwk = $this->getJWK($configuration);

if ($jwk === null) {
Expand Down
5 changes: 5 additions & 0 deletions lib/Service/CallService.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ public function call(
// Set authentication if needed. @todo: create the authentication service
//$createCertificates && $this->getCertificate($config);

// Make sure to filter out all the authentication variables / secrets.
$config = array_filter($config, function ($key) {
return str_contains(strtolower($key), 'authentication') === false;
}, ARRAY_FILTER_USE_KEY);

// Let's log the call.
$this->source->setLastCall(new \DateTime());
// @todo: save the source
Expand Down
16 changes: 14 additions & 2 deletions lib/Service/SynchronizationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,12 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is

// Make the initial API call
$response = $this->callService->call(source: $source, endpoint: $endpoint, method: 'GET', config: $config)->getResponse();
$lastHash = md5($response['body']);
$body = json_decode($response['body'], true);
if (empty($body) === true) {
// @todo log that we got a empty response
return [];
}
$objects = array_merge($objects, $this->getAllObjectsFromArray(array: $body, synchronization: $synchronization));

// Return a single object or empty array if in test mode
Expand Down Expand Up @@ -412,6 +417,13 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is
do {
$config = $this->getNextPage(config: $config, sourceConfig: $sourceConfig, currentPage: $currentPage);
$response = $this->callService->call(source: $source, endpoint: $endpoint, method: 'GET', config: $config)->getResponse();
$hash = md5($response['body']);

if($hash === $lastHash) {
break;
}

$lastHash = $hash;
$body = json_decode($response['body'], true);

if (empty($body) === true) {
Expand Down Expand Up @@ -484,8 +496,8 @@ public function getAllObjectsFromArray(array $array, Synchronization $synchroniz
$sourceConfig = $synchronization->getSourceConfig();

// Check if a specific objects position is defined in the source configuration
if (empty($sourceConfig['objectsPosition']) === false) {
$position = $sourceConfig['objectsPosition'];
if (empty($sourceConfig['resultsPosition']) === false) {
$position = $sourceConfig['resultsPosition'];
// Use Dot notation to access nested array elements
$dot = new Dot($array);
if ($dot->has($position) === true) {
Expand Down
15 changes: 13 additions & 2 deletions lib/Twig/AuthenticationRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

namespace OCA\OpenConnector\Twig;

use Adbar\Dot;
use GuzzleHttp\Exception\GuzzleException;
use OCA\OpenConnector\Db\Source;
use OCA\OpenConnector\Service\AuthenticationService;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Twig\Extension\RuntimeExtensionInterface;

class AuthenticationRuntime implements RuntimeExtensionInterface
Expand All @@ -25,8 +28,12 @@ public function __construct(
*/
public function oauthToken(Source $source): string
{
$configuration = new Dot($source->getConfiguration(), true);

$authConfig = $configuration->get('authentication');

return $this->authService->fetchOAuthTokens(
configuration: $source->getAuthenticationConfig()
configuration: $authConfig
);
}

Expand All @@ -39,8 +46,12 @@ public function oauthToken(Source $source): string
*/
public function jwtToken(Source $source): string
{
$configuration = new Dot($source->getConfiguration(), true);

$authConfig = $configuration->get('authentication');

return $this->authService->fetchJWTToken(
configuration: $source->getAuthenticationConfig()
configuration: $authConfig
);
}
}
8 changes: 4 additions & 4 deletions src/entities/synchronization/synchronization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ export class Synchronization extends ReadonlyBaseClass implements TSynchronizati
public sourceType: string
public sourceHash: string
public sourceTargetMapping: string
public sourceConfig: object
public sourceConfig: Record<string, string>
public sourceLastChanged: string
public sourceLastChecked: string
public sourceLastSynced: string
public targetId: string
public targetType: string
public targetHash: string
public targetSourceMapping: string
public targetConfig: object
public targetConfig: Record<string, string>
public targetLastChanged: string
public targetLastChecked: string
public targetLastSynced: string
Expand Down Expand Up @@ -64,15 +64,15 @@ export class Synchronization extends ReadonlyBaseClass implements TSynchronizati
sourceType: z.string(),
sourceHash: z.string(),
sourceTargetMapping: z.string(),
sourceConfig: z.object({}),
sourceConfig: z.record(z.string(), z.string()),
sourceLastChanged: z.string(),
sourceLastChecked: z.string(),
sourceLastSynced: z.string(),
targetId: z.string(),
targetType: z.string(),
targetHash: z.string(),
targetSourceMapping: z.string(),
targetConfig: z.object({}),
targetConfig: z.record(z.string(), z.string()),
targetLastChanged: z.string(),
targetLastChecked: z.string(),
targetLastSynced: z.string(),
Expand Down
4 changes: 2 additions & 2 deletions src/entities/synchronization/synchronization.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ export type TSynchronization = {
sourceType: string
sourceHash: string
sourceTargetMapping: string
sourceConfig: object
sourceConfig: Record<string, string>
sourceLastChanged: string
sourceLastChecked: string
sourceLastSynced: string
targetId: string
targetType: string
targetHash: string
targetSourceMapping: string
targetConfig: object
targetConfig: Record<string, string>
targetLastChanged: string
targetLastChecked: string
targetLastSynced: string
Expand Down
28 changes: 21 additions & 7 deletions src/modals/Log/ViewSynchronizationContract.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@ import { logStore, navigationStore } from '../../store/store.js'
</div>

<strong>Standard</strong>
<table>
<tr v-for="(value, key) in standardItems"

:key="key">
<table class="table">
<tr v-for="(value, key) in standardItems" :key="key">
<td class="keyColumn">
{{ key }}
<b>{{ key }}</b>
</td>
<td v-if="typeof value === 'string' && (key === 'created' || key === 'updated' || key === 'expires' || key === 'lastRun' || key === 'nextRun')">

<td v-if="typeof value === 'string' && getValidISOstring(value)">
{{ new Date(value).toLocaleString() }}
</td>
<td v-else>
{{ value }}
{{ value || '-' }}
</td>
</tr>
</table>
Expand All @@ -37,6 +36,8 @@ import {
NcModal,
} from '@nextcloud/vue'
import getValidISOstring from '../../services/getValidISOstring.js'
export default {
name: 'ViewSynchronizationContract',
components: {
Expand Down Expand Up @@ -107,3 +108,16 @@ export default {
}
</style>

<style scoped>
.table {
border: 1px solid grey; /* Add a grey border around the table */
border-collapse: collapse; /* Ensure borders are collapsed for a cleaner look */
width: 100%; /* Optional: make the table take full width */
}
.table td, .table th {
border: 1px solid grey; /* Add a grey border around each cell */
padding: 8px; /* Add padding to table cells */
}
</style>
Loading

0 comments on commit c9efb3f

Please sign in to comment.