- Implemented `KekaController` to manage OAuth flows for Keka HR, including connection, callback, and disconnection. - Added required routes, middleware updates, and session handling for Keka integration. - Enhanced dashboard UI to display Keka connection status and provide seamless connect/disconnect options. - Included validation and configuration for Keka Portal URL in connected apps. - Developed feature tests to ensure reliable authentication and token handling workflows for Keka.
210 lines
6.6 KiB
PHP
210 lines
6.6 KiB
PHP
<?php
|
|
|
|
use App\Concerns\HandlesOperations;
|
|
use App\Data\ConnectedApp\ConnectAppRequest;
|
|
use App\Data\ConnectedApp\ConnectionStatusData;
|
|
use App\Enums\ConnectionStatusEnum;
|
|
use App\Livewire\Forms\StoreConnectedAppFrom;
|
|
use App\Models\{ConnectionProtocol, ConnectionProvider, ConnectionStatus};
|
|
use App\Services\{
|
|
ConnectedAppService,
|
|
ConnectionProtocolService,
|
|
ConnectionProviderService,
|
|
ConnectionStatusService,
|
|
};
|
|
use Illuminate\Support\Collection;
|
|
use Livewire\Attributes\{Computed, Title};
|
|
use Livewire\Component;
|
|
use Mary\Traits\Toast;
|
|
use Spatie\LaravelData\DataCollection;
|
|
|
|
new #[Title("Create a service")]
|
|
class extends Component {
|
|
use HandlesOperations;
|
|
|
|
public StoreConnectedAppFrom $form;
|
|
protected ConnectedAppService $appService;
|
|
|
|
public function boot(ConnectedAppService $appService): void
|
|
{
|
|
$this->appService = $appService;
|
|
}
|
|
|
|
#[Computed]
|
|
public function providers()
|
|
{
|
|
return ConnectionProvider::query()->get();
|
|
}
|
|
|
|
#[Computed]
|
|
public function protocols(): Collection
|
|
{
|
|
$protocolService = app(ConnectionProtocolService::class);
|
|
|
|
return $protocolService
|
|
->getAll()
|
|
->map(function (ConnectionProtocol $protocol) {
|
|
$protocol->name = strtoupper($protocol->name);
|
|
return $protocol;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @returns DataCollection<int, ConnectionStatus>
|
|
*/
|
|
#[Computed]
|
|
public function connectionStatuses(): DataCollection
|
|
{
|
|
$statusService = app(ConnectionStatusService::class);
|
|
|
|
return $statusService
|
|
->getAll()
|
|
->map(function (ConnectionStatusData $status) {
|
|
if ($status->name === ConnectionStatusEnum::Disconnected) {
|
|
$this->form->statusId = $status->id;
|
|
}
|
|
return $status;
|
|
});
|
|
}
|
|
|
|
#[Computed]
|
|
public function isSaml(): bool
|
|
{
|
|
$protocol = $this->protocols()->firstWhere(
|
|
"id",
|
|
(int) $this->form->protocolId,
|
|
);
|
|
return $protocol && strtolower($protocol->name) === "saml";
|
|
}
|
|
|
|
#[Computed]
|
|
public function isKekaProvider(): bool
|
|
{
|
|
$provider = $this->providers()->firstWhere(
|
|
"id",
|
|
(int) $this->form->providerId,
|
|
);
|
|
return $provider && $provider->slug === "keka-hr";
|
|
}
|
|
|
|
public function save(bool $goBack = true): void
|
|
{
|
|
$this->form->validate();
|
|
|
|
$settings = null;
|
|
if ($this->isSaml()) {
|
|
$attributes = [];
|
|
foreach ($this->form->samlAttributes as $attr) {
|
|
if (!empty($attr['saml_name']) && !empty($attr['user_field'])) {
|
|
$attributes[$attr['saml_name']] = $attr['user_field'];
|
|
}
|
|
}
|
|
$settings = [
|
|
"saml" => [
|
|
"entity_id" => $this->form->samlEntityId,
|
|
"acs_url" => $this->form->samlAcsUrl,
|
|
"nameid_format" => $this->form->samlNameIdFormat,
|
|
"nameid_source" => $this->form->samlNameIdSource,
|
|
"attributes" => $attributes,
|
|
],
|
|
];
|
|
} elseif ($this->isKekaProvider()) {
|
|
$settings = [
|
|
"keka_url" => $this->form->kekaUrl,
|
|
"keka" => [
|
|
"keka_url" => $this->form->kekaUrl,
|
|
"callback_path" => "signin-oidc",
|
|
"provider" => "Office365",
|
|
],
|
|
];
|
|
}
|
|
|
|
$data = new ConnectAppRequest(
|
|
name: $this->form->name,
|
|
connectionProviderId: $this->form->providerId,
|
|
connectionProtocolId: $this->form->protocolId,
|
|
slug: str($this->form->name)->slug()->toString(),
|
|
connectionStatusId: $this->form->statusId,
|
|
settings: $settings,
|
|
);
|
|
$this->attempt(
|
|
action: function () use ($data, $goBack) {
|
|
$this->appService->create($data);
|
|
$this->form->reset();
|
|
if ($goBack) {
|
|
$this->goBack();
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
public function addSamlAttribute(): void
|
|
{
|
|
$this->form->addSamlAttribute();
|
|
}
|
|
|
|
public function removeSamlAttribute(int $index): void
|
|
{
|
|
$this->form->removeSamlAttribute($index);
|
|
}
|
|
|
|
public function goBack(): void
|
|
{
|
|
$this->redirectRoute("apps.index", navigate: true);
|
|
}
|
|
};
|
|
?>
|
|
|
|
<div>
|
|
<x-shared.card :title="__('Create a service')" class="">
|
|
<x-mary-form wire:submit="save" class="p-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<x-mary-input wire:model.live.blur="form.name" required :label="__('Name')"/>
|
|
<x-mary-choices
|
|
required
|
|
wire:model.live="form.protocolId"
|
|
:label="__('Protocol')"
|
|
:single="true"
|
|
:options="$this->protocols"
|
|
placeholder="Select"
|
|
/>
|
|
<x-mary-choices
|
|
required
|
|
wire:model.live="form.providerId"
|
|
:label="__('Provider')"
|
|
placeholder="Select"
|
|
:single="true"
|
|
:options="$this->providers"
|
|
/>
|
|
<x-mary-choices
|
|
required
|
|
wire:model="form.statusId"
|
|
:label="__('Status')"
|
|
:single="true"
|
|
:options="$this->connectionStatuses->toArray()"
|
|
placeholder="Select"
|
|
/>
|
|
|
|
@if($this->isSaml)
|
|
<x-dashboard.connected-apps.saml-configuration :form="$this->form"/>
|
|
@endif
|
|
|
|
@if($this->isKekaProvider)
|
|
<div class="md:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-4 border-t border-gray-200 pt-4 mt-2">
|
|
<h3 class="font-semibold text-sm text-blue-600 md:col-span-2">Keka Configuration</h3>
|
|
<x-mary-input wire:model="form.kekaUrl" required :label="__('Keka Portal URL')"
|
|
placeholder="e.g. https://sentientgeeks.keka.com" class="md:col-span-2"/>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="md:col-span-2 flex gap-x-4 font-medium justify-end">
|
|
<x-mary-button wire:click.prevent="goBack" spinner="goBack">Cancel</x-mary-button>
|
|
<x-mary-button wire:click.prevent="save(false)">Save & Add Another</x-mary-button>
|
|
<x-mary-button type="submit"
|
|
class="btn-primary">
|
|
Save
|
|
</x-mary-button>
|
|
</div>
|
|
</x-mary-form>
|
|
</x-shared.card>
|
|
</div>
|