Skip to content

Commit

Permalink
Merge pull request #255 from limanmys/2.1-dev
Browse files Browse the repository at this point in the history
2.1 Release

feat: Fully JWT authentication end to end
070f551
fix: Extension role changing keeps functions
5dad41b
fix: Helm chart issues
35e5afb
chore: Another update on deployment files
82945b8
Merge branch 'master' into 2.1-dev
d92e410
fix: All extension functions that is broken from JWT transition
f84749d
feat: Detailed license information
5d5db99
feat: Log rotation page fixes
aeb0bb6
chore: Code cleanup
274989a
chore: Changed default user model location
0ff9ec7
fix: Added missing function
daa09c4
fix: Certificate retrieving issues on FQDN based hosts
91e8744
feat: Updated limanctl executable
b52b83f
feat: Laravel 10 update
0dbceb4
fix: RHEL 8.10 compability fixes
6e1a6b0
fix: Menu json response error
e33456d
feat-wip: View modifier role system
d44d47c
feat-wip: Role based view customization system
b9eb714
fix: Keycloak users cannot be deleted
9aab915
feat: Username login support
a6a9327
feat: View role system
24bde45
feat: Extension left menu support
e636db8
chore: Rebuild
4f7f853
feat: Keycloak role permission system
51d1d01
  • Loading branch information
dogukanoksuz authored Sep 13, 2024
2 parents 62c3f62 + 4d8c160 commit 4a95017
Show file tree
Hide file tree
Showing 77 changed files with 2,026 additions and 1,815 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: Docker
on: [push]
on:
push:
branches:
- master

env:
# Use docker.io for Docker Hub if empty
Expand Down
15 changes: 2 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,15 @@ RUN apt -yqq update
RUN DEBIAN_FRONTEND=noninteractive apt -yqq install sudo nodejs gpg zip unzip nginx sysstat php8.1-redis php8.1-fpm php8.1-gd php8.1-curl php8.1 php8.1-sqlite3 php8.1-snmp php8.1-mbstring php8.1-xml php8.1-zip php8.1-posix libnginx-mod-http-headers-more-filter libssl3 supervisor php8.1-pgsql pgloader php8.1-bcmath rsync dnsutils php8.1-ldap php8.1-smbclient krb5-user php8.1-ssh2 smbclient novnc

# FILES
RUN bash -c 'mkdir -p /liman_files/{server,certs,logs,database,sandbox,keys,extensions,modules,packages,ui}'
RUN bash -c 'mkdir -p /liman_files/{server,certs,logs,database,sandbox,keys,extensions,packages,ui}'

# UI
RUN curl -s https://api.github.com/repos/limanmys/next/releases/latest | grep "browser_download_url.*zip" | cut -d : -f 2,3 | tr -d \" | wget -qi -
RUN unzip ui*.zip -d ui
RUN mv ui /liman_files/

# CORE
RUN wget "https://github.com/limanmys/core/archive/refs/heads/master.zip" -O "core.zip"
RUN unzip -qq core.zip
RUN mv core-master/* /liman_files/server
RUN mv core-master/.env.example /liman_files/server
RUN rm -rf core.zip
COPY . /liman_files/server

# PHP SANDBOX
RUN wget "https://github.com/limanmys/php-sandbox/archive/refs/heads/master.zip" -O "sandbox.zip"
Expand All @@ -44,13 +40,6 @@ RUN mkdir -p /liman_files/sandbox/php
RUN mv php-sandbox-master/* /liman_files/sandbox/php/
RUN rm -rf sandbox.zip php-sandbox-master

# EXT TEMPLATES
RUN wget "https://github.com/limanmys/extension_templates/archive/master.zip" -O "extension_templates.zip"
RUN unzip -qq extension_templates.zip
RUN mkdir -p /liman_files/server/storage/extension_templates
RUN mv extension_templates-master/* /liman_files/server/storage/extension_templates
RUN rm -rf extension_templates.zip extension_templates-master

# RENDER ENGINE
RUN curl -s https://api.github.com/repos/limanmys/fiber-render-engine/releases/latest | grep "browser_download_url.*zip" | cut -d : -f 2,3 | tr -d \" | wget -qi -
RUN unzip liman_render*.zip
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ Liman is sponsored by [HAVELSAN](https://havelsan.com.tr/en).
MIT License

See [LICENSE](https://github.com/limanmys/core/blob/master/LICENSE) the full text.

65 changes: 56 additions & 9 deletions app/Classes/Authentication/Authenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use App\Models\AuthLog;
use App\Models\Permission;
use App\User;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
Expand All @@ -20,21 +20,23 @@ class Authenticator
*/
public static function createNewToken($token, ?Request $request = null)
{
User::find(auth('api')->user()->id)->update([
$id = auth('api')->user()->id;

User::find($id)->update([
'last_login_at' => Carbon::now()->toDateTimeString(),
'last_login_ip' => $request->ip(),
]);

AuthLog::create([
'user_id' => auth('api')->user()->id,
'user_id' => $id,
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
]);

$return = [
'expired_at' => (auth('api')->factory()->getTTL() * 60 + time()) * 1000,
'user' => [
...User::find(auth('api')->user()->id, [
...User::find($id, [
'id',
'name',
'email',
Expand All @@ -45,11 +47,56 @@ public static function createNewToken($token, ?Request $request = null)
'last_login_at' => Carbon::now()->toDateTimeString(),
'last_login_ip' => $request->ip(),
'permissions' => [
'server_details' => Permission::can(auth('api')->user()->id, 'liman', 'id', 'server_details'),
'server_services' => Permission::can(auth('api')->user()->id, 'liman', 'id', 'server_services'),
'add_server' => Permission::can(auth('api')->user()->id, 'liman', 'id', 'add_server'),
'update_server' => Permission::can(auth('api')->user()->id, 'liman', 'id', 'update_server'),
'view_logs' => Permission::can(auth('api')->user()->id, 'liman', 'id', 'view_logs'),
'server_details' => Permission::can($id, 'liman', 'id', 'server_details'),
'server_services' => Permission::can($id, 'liman', 'id', 'server_services'),
'add_server' => Permission::can($id, 'liman', 'id', 'add_server'),
'update_server' => Permission::can($id, 'liman', 'id', 'update_server'),
'view_logs' => Permission::can($id, 'liman', 'id', 'view_logs'),
'view' => (function () {
$defaultPermissions = config('liman.default_views');

if (auth('api')->user()->isAdmin()) {
$defaultPermissions["dashboard"][] = "auth_logs";
$defaultPermissions["dashboard"][] = "extensions";
return $defaultPermissions;
}

$permissions = Permission::whereIn(
'morph_id',
auth('api')->user()->roles->pluck('id')->toArray()
)
->where('morph_type', 'roles')
->where('type', 'view')
->get();

$viewPermissions = [
...$defaultPermissions,
];

$dashboardPermissions = [];
$permissions->map(function ($permission) use (&$dashboardPermissions, &$viewPermissions) {
if ($permission->key === "sidebar") {
// if sidebar is set to extensions, you cannot override it.
if (isset($viewPermissions["sidebar"]) && $viewPermissions["sidebar"] === "extensions") {
return;
}
$viewPermissions["sidebar"] = json_decode($permission->value);
}

if ($permission->key === "dashboard") {
// merge all dashboard permissions that comes from roles
$dashboardPermissions = array_unique([
...$dashboardPermissions,
...json_decode($permission->value),
]);
}
});

// if there is no dashboard permission, set it to default
$viewPermissions["dashboard"] = count($dashboardPermissions) > 0 ? $dashboardPermissions : $defaultPermissions["dashboard"];

return $viewPermissions;
})(),
],
],
];
Expand Down
99 changes: 59 additions & 40 deletions app/Classes/Authentication/KeycloakAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,77 +3,96 @@
namespace App\Classes\Authentication;

use App\Models\Oauth2Token;
use App\User;
use GuzzleHttp\Client;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Keycloak\KeycloakClient;
use Keycloak\User\UserApi;
use Stevenmaguire\OAuth2\Client\Provider\Keycloak as KeycloakProvider;

class KeycloakAuthenticator implements AuthenticatorInterface
{
public function authenticate($credentials, $request): JsonResponse
private $kcClient;

private $oauthProvider;

private $kcUserApi;

public function __construct()
{
$client = new Client([
'verify' => false,
$this->kcClient = new KeycloakClient(
env('KEYCLOAK_CLIENT_ID'),
env('KEYCLOAK_CLIENT_SECRET'),
env('KEYCLOAK_REALM'),
env('KEYCLOAK_BASE_URL'),
null,
''
);

$this->kcUserApi = new UserApi($this->kcClient);

$this->oauthProvider = new KeycloakProvider([
'authServerUrl' => env('KEYCLOAK_BASE_URL'),
'realm' => env('KEYCLOAK_REALM'),
'clientId' => env('KEYCLOAK_CLIENT_ID'),
'clientSecret' => env('KEYCLOAK_CLIENT_SECRET'),
'redirectUri' => env('KEYCLOAK_REDIRECT_URI'),
'version' => '24.0.0',
]);
}

public function authenticate($credentials, $request): JsonResponse
{
try {
$r = $client->post(
env('KEYCLOAK_BASE_URL').'/realms/'.env('KEYCLOAK_REALM').'/protocol/openid-connect/token',
[
'form_params' => [
'client_id' => env('KEYCLOAK_CLIENT_ID'),
'client_secret' => env('KEYCLOAK_CLIENT_SECRET'),
'username' => $request->email,
'password' => $request->password,
'grant_type' => 'password',
'scope' => 'openid',
],
]
);
} catch (\Exception $e) {
Log::error('Keycloak authentication failed. '.$e->getMessage());
$accessTokenObject = $this->oauthProvider->getAccessToken('password', [
'username' => $request->email,
'password' => $request->password,
'scope' => 'openid',
]);

return Authenticator::returnLoginError($request->email);
}
$resourceOwner = $this->oauthProvider->getResourceOwner($accessTokenObject);

$response = json_decode($r->getBody()->getContents(), true);
if (! isset($response['access_token'])) {
Log::error('Keycloak authentication failed. Access token is missing.');
$roles = collect($this->kcUserApi->getRoles($resourceOwner->getId()))
->map(function ($role) {
return $role->name;
})->toArray();
} catch (\Exception $e) {
Log::error('Keycloak authentication failed. '.$e->getMessage());

return Authenticator::returnLoginError($request->email);
}
$details = json_decode(base64_decode(str_replace('_', '/', str_replace('-', '+', explode('.', $response['access_token'])[1]))));

$create = User::where('email', strtolower($request->email))
->orWhere('username', strtolower($request->email))
->first();

if (! $create) {
$user = User::create([
'id' => $details->sub,
'name' => $details->name,
'email' => $details->email,
'username' => $details->preferred_username,
'id' => $resourceOwner->getId(),
'name' => $resourceOwner->getName(),
'email' => $resourceOwner->getEmail(),
'username' => $resourceOwner->getUsername(),
'auth_type' => 'keycloak',
'password' => Hash::make(Str::random(16)),
'password' => Hash::make(Str::uuid()),
'forceChange' => false,
]);
} else {
$user = User::where('id', $details->sub)->first();
$user = User::where('id', $resourceOwner->getId())->first();
}

Oauth2Token::updateOrCreate([
'user_id' => $details->sub,
'token_type' => $response['token_type'],
'user_id' => $resourceOwner->getId(),
'token_type' => $accessTokenObject->getValues()['token_type'],
], [
'user_id' => $details->sub,
'token_type' => $response['token_type'],
'access_token' => $response['access_token'],
'refresh_token' => $response['refresh_token'],
'expires_in' => (int) $response['expires_in'],
'refresh_expires_in' => (int) $response['refresh_expires_in'],
'user_id' => $resourceOwner->getId(),
'token_type' => $accessTokenObject->getValues()['token_type'],
'access_token' => $accessTokenObject->getToken(),
'refresh_token' => $accessTokenObject->getRefreshToken(),
'expires_in' => $accessTokenObject->getExpires(),
'refresh_expires_in' => $accessTokenObject->getValues()['refresh_expires_in'],
'permissions' => $roles,
]);

return Authenticator::createNewToken(
Expand Down
2 changes: 1 addition & 1 deletion app/Classes/Authentication/LDAPAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use App\Models\RoleUser;
use App\Models\Server;
use App\Models\UserSettings;
use App\User;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
Expand Down
11 changes: 11 additions & 0 deletions app/Classes/Authentication/LimanAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@

namespace App\Classes\Authentication;

use App\Models\User;
use Illuminate\Http\JsonResponse;

class LimanAuthenticator implements AuthenticatorInterface
{
public function authenticate($credentials, $request): JsonResponse
{
$user = User::where("email", $credentials["email"])
->orWhere("username", $credentials["email"])
->first();

if (! $user) {
return response()->json(['message' => 'Kullanıcı adı veya şifreniz yanlış.'], 401);
}

$credentials["email"] = $user->email;

$token = auth('api')->attempt($credentials);
if (! $token) {
return response()->json(['message' => 'Kullanıcı adı veya şifreniz yanlış.'], 401);
Expand Down
Loading

0 comments on commit 4a95017

Please sign in to comment.