Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.1 Release #255

Merged
merged 25 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
070f551
feat: Fully JWT authentication end to end
dogukanoksuz Jul 19, 2024
5dad41b
fix: Extension role changing keeps functions
dogukanoksuz Jul 22, 2024
35e5afb
fix: Helm chart issues
zekiahmetbayar Jul 26, 2024
82945b8
chore: Another update on deployment files
zekiahmetbayar Jul 30, 2024
d92e410
Merge branch 'master' into 2.1-dev
dogukanoksuz Jul 30, 2024
f84749d
fix: All extension functions that is broken from JWT transition
dogukanoksuz Jul 30, 2024
5d5db99
feat: Detailed license information
dogukanoksuz Aug 1, 2024
aeb0bb6
feat: Log rotation page fixes
dogukanoksuz Aug 12, 2024
274989a
chore: Code cleanup
dogukanoksuz Aug 13, 2024
0ff9ec7
chore: Changed default user model location
dogukanoksuz Aug 13, 2024
daa09c4
fix: Added missing function
dogukanoksuz Aug 26, 2024
91e8744
fix: Certificate retrieving issues on FQDN based hosts
dogukanoksuz Aug 28, 2024
b52b83f
feat: Updated limanctl executable
dogukanoksuz Aug 28, 2024
0dbceb4
feat: Laravel 10 update
dogukanoksuz Sep 2, 2024
6e1a6b0
fix: RHEL 8.10 compability fixes
dogukanoksuz Sep 3, 2024
e33456d
fix: Menu json response error
dogukanoksuz Sep 3, 2024
d44d47c
feat-wip: View modifier role system
dogukanoksuz Sep 4, 2024
b9eb714
feat-wip: Role based view customization system
dogukanoksuz Sep 5, 2024
9aab915
fix: Keycloak users cannot be deleted
dogukanoksuz Sep 6, 2024
a6a9327
feat: Username login support
dogukanoksuz Sep 6, 2024
24bde45
feat: View role system
dogukanoksuz Sep 6, 2024
e636db8
feat: Extension left menu support
dogukanoksuz Sep 9, 2024
4f7f853
chore: Rebuild
dogukanoksuz Sep 10, 2024
51d1d01
feat: Keycloak role permission system
dogukanoksuz Sep 10, 2024
4d8c160
feat: 2.1 Release
dogukanoksuz Sep 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading