Skip to content

Commit

Permalink
Merge pull request #116 from bcgov/test
Browse files Browse the repository at this point in the history
Test
  • Loading branch information
kmarshgov authored Oct 22, 2024
2 parents d545dbe + 80d6066 commit daca133
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 0 deletions.
100 changes: 100 additions & 0 deletions app/Http/Controllers/ServiceAccountController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Response;

class ServiceAccountController extends Controller
{
public function fetchData(Request $request)
{
$tableName = $request->input('table', '');
$app = $request->input('app');
$where = $request->input('q');
$orderBy = $request->input('order');
$perPage = $request->input('per_page') ?? 5000;
$connection = env('DB_DATABASE_' . strtoupper($app));

// Base query with parameter placeholders
$query = DB::connection($connection)->table(strtolower($tableName));

// Add WHERE clause if provided
if(isset($where)) {
foreach ($where as $condition) {
// Extract the column name, operator, and value from the condition
$columnName = $condition['column'];
$operator = $condition['operator'];
$value = $condition['value'];

// Add the where condition to the query
$query->where($columnName, $operator, $value);
}
}

// Add ORDER BY clause if provided
if(isset($orderBy)) {
$orderBy = explode("~", $orderBy);
if(sizeof($orderBy) === 2) {
$query->orderBy($orderBy[0], $orderBy[1]);
}
}

// pagination parameters
$currentPage = $request->input('page', 1); // Current page, default is 1
$offset = ($currentPage - 1) * $perPage;

// append LIMIT and OFFSET to the query
$query->limit($perPage)->offset($offset);

// Execute the query with parameter bindings
try {
$data = $query->get();
} catch (\Exception $exception) {
return response()->json(['status' => false, 'body' => $exception->errorInfo[0]]);
}

// Fetch total count for pagination
$totalCountQuery = "SELECT COUNT(*) AS total FROM " . strtolower($tableName);
$totalCount = DB::connection($connection)->selectOne($totalCountQuery);

// Create pagination object
$paginatedData = new LengthAwarePaginator(
$data, $totalCount->total, $perPage, $currentPage
);


return response()->json(['status' => true, 'body' => $paginatedData]);
}

public function fetchTables(Request $request)
{
$app = $request->input('app');

try {
$tables = DB::connection(env('DB_DATABASE_' . strtoupper($app)))
->select("SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema='public'");
} catch (\Exception $exception) {
return response()->json(['status' => false, 'body' => $exception->errorInfo[0]], 200);
}

return Response::json(['status' => true, 'body' => $tables], 200);
}

public function fetchColumns(Request $request)
{
$tableName = $request->input('table');
$app = $request->input('app');
try {
$columns = Schema::connection(env('DB_DATABASE_' . strtoupper($app)))
->getColumns(strtolower($tableName));
} catch (\Exception $exception) {
return response()->json(['status' => false, 'body' => $exception->errorInfo[0]], 200);
}

return Response::json(['status' => true, 'body' => $columns], 200);
}
}
8 changes: 8 additions & 0 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

'api2' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\App\Http\Middleware\Api2Middleware::class,
],
];

/**
Expand Down Expand Up @@ -72,5 +77,8 @@ class Kernel extends HttpKernel
'institution_admin' => \Modules\Institution\App\Http\Middleware\IsAdmin::class,
'super_admin' => \App\Http\Middleware\SuperAdmin::class,

'apiauth' => \App\Http\Middleware\ApiAuth::class,


];
}
20 changes: 20 additions & 0 deletions app/Http/Middleware/Api2Middleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Api2Middleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
}
67 changes: 67 additions & 0 deletions app/Http/Middleware/ApiAuth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace App\Http\Middleware;

use Closure;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Http\Request;
use Response;

class ApiAuth
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @param string|null ...$roles
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, ...$roles)
{
$forwardedHost = $request->headers->get('X-Forwarded-Host');

// Prevent access to the API except via the gateway
if ($forwardedHost !== env('KEYCLOAK_APS_URL')) {
return Response::json(['error' => 'Unauthorized.' . $forwardedHost . ',,,' . env('KEYCLOAK_APS_URL')], 403);
}

$token = request()->bearerToken();
if(is_null($token)){
return Response::json(['status' => false, 'error' => 'Missing token.'], 401);
}
$jwksUri = env('KEYCLOAK_APS_ISS') . env('KEYCLOAK_APS_CERT_PATH');
$jwksJson = file_get_contents($jwksUri);
$jwksData = json_decode($jwksJson, true);
$matchingKey = null;
foreach ($jwksData['keys'] as $key) {
if (isset($key['use']) && $key['use'] === 'sig') {
$matchingKey = $key;
break;
}
}

$wrappedPk = wordwrap($matchingKey['x5c'][0], 64, "\n", true);
$pk = "-----BEGIN CERTIFICATE-----\n" . $wrappedPk . "\n-----END CERTIFICATE-----";

try {
$decoded = JWT::decode($token, new Key($pk, 'RS256'));
} catch (ExpiredException $e) {
return Response::json(['status' => false, 'error' => 'Token has expired.'], 401);
} catch (\Exception $e) {
return Response::json(['status' => false, 'error' => "An error occurred: " . $e->getMessage()], 401);
}

if(is_null($decoded)) {
return Response::json(['status' => false, 'error' => "Invalid token."], 401);
}else{
//only validate for accounts that we have registered
if($decoded->iss === env('KEYCLOAK_APS_ISS')){
return $next($request);
}
}

return Response::json(['status' => false, 'error' => "Generic error."], 403);
}
}
4 changes: 4 additions & 0 deletions app/Providers/RouteServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public function boot(): void
});

$this->routes(function () {
Route::prefix('api2')
->middleware('api2')
->group(base_path('routes/api2.php'));

Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Expand Down
21 changes: 21 additions & 0 deletions routes/api2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

use App\Http\Controllers\ServiceAccountController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::group(['middleware' => 'apiauth'], function () {
Route::post('/fetch', [ServiceAccountController::class, 'fetchData']);
Route::post('/tables', [ServiceAccountController::class, 'fetchTables']);
Route::post('/columns', [ServiceAccountController::class, 'fetchColumns']);
});

0 comments on commit daca133

Please sign in to comment.