...
 
Commits (8)
......@@ -36,7 +36,7 @@ def bootstrap(name='MicroService', *args, **kwargs):
app.add_api(oai, resolver=MultipleResourceResolver(
'api', collection_endpoint_name="index"), validate_responses=True)
monkeypatch()
return app
monkeypatch()
from flask import request, redirect, abort
import Util
import json
import jwt
import logging
logger = logging.getLogger()
def post():
try:
master_jwt = request.json.get("jwt")
unverified = jwt.decode(master_jwt, verify=False)
servicename = unverified.get("servicename")
service = Util.tokenService.getService(servicename, clean=True)
master_data = jwt.decode(
master_jwt, service.client_secret, algorithms="HS256")
logger.debug("jwt: {}, decoded: {}".format(master_jwt, master_data))
userId = master_data.get("userId")
code = master_data.get("code")
logger.debug("code: {}, userId: {}".format(code, userId))
state_jwt = master_data.get("state")
state_data = jwt.decode(
state_jwt, Util.tokenService.secret, algorithms="HS256")
logger.debug("state: {}, decoded state: {}".format(
state_jwt, state_data))
Util.tokenService.exchangeAuthCodeToAccessToken(
code, Util.tokenService.getService(state_data["servicename"], clean=True), user=userId)
return "", 204
except Exception as e:
logger.exception(e)
abort(400)
......@@ -96,7 +96,7 @@ class TokenService():
svc = service
break
if not svc:
if svc is None:
svc = self.refreshService(servicename)
if clean:
......
......@@ -73,8 +73,9 @@ class Test_TokenServiceServer(unittest.TestCase):
'GET', f"/service/{service.servicename}"
) .will_respond_with(200, body=service.to_json())
response = self.client.get(
f"/port-service/service/{service.servicename}")
with pact:
response = self.client.get(
f"/port-service/service/{service.servicename}")
self.assertEqual(response.status_code, 200,
msg=response.get_data(as_text=True))
......@@ -110,13 +111,30 @@ class Test_TokenServiceServer(unittest.TestCase):
}
import base64
import json
state = jwt.encode(data, key, algorithm="HS256")
stateReal = jwt.encode(data, key, algorithm="HS256")
state = base64.b64encode(json.dumps(
{"jwt": state.decode("utf-8"), "user": user.username}).encode("utf-8"))
{"jwt": stateReal.decode("utf-8"), "user": user.username}).encode("utf-8"))
pluginDict = {
"servicename": service.servicename,
"state": stateReal.decode("utf-8"),
"userId": user.username,
"code": code
}
jwtEncode = jwt.encode(
pluginDict, service.client_secret, algorithm="HS256")
# need pact for service from Token Storage
pact.given(
'An oauthservice was registered.'
'An oauthservice was registered again.'
).upon_receiving(
'A request to get this oauthservice.'
).with_request(
'GET', f"/service/{service.servicename}"
) .will_respond_with(200, body=service.to_json())
pact.given(
'An oauthservice was registered again 2.'
).upon_receiving(
'A request to get this oauthservice.'
).with_request(
......@@ -146,12 +164,10 @@ class Test_TokenServiceServer(unittest.TestCase):
) .will_respond_with(201, body={"success": True})
with pact:
response = self.client.get(
"/port-service/redirect", query_string={"code": code, "state": state})
response = self.client.post(
"/port-service/exchange", json={"jwt": jwtEncode.decode("utf-8")})
self.assertEqual(response.status_code, 302, msg=response.get_data())
self.assertEqual(
response.headers["location"], "http://localhost/port-service/authorization-success", msg=response.get_data())
self.assertEqual(response.status_code, 204, msg=response.get_data())
# TODO: add tests here for redirects to cancel page
# test for no service found
......
......@@ -257,6 +257,35 @@ paths:
responses:
'204':
description: No Content
/exchange:
post:
summary: Exchange the oauth2 code
tags: []
responses:
'204':
description: No Content
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
jwt:
type: string
description: |-
JWT holds the following object with keys:
state, code, userId, servicename
state holds all informations from rds.
code holds the code, which needs to be exchanged.
userId holds the name of the user, which get the exchanged token. If the userId is not known as master account in token storage, it will be created as master account associated with servicename.
servicename holds the name of the service, which client secret was used to sign the jwt.
The jwt has to be signed with the oauth2 client secret for rds from service.
description: ''
components:
schemas:
oauth_uri:
......
......@@ -34,10 +34,12 @@ $application->registerRoutes( $this, [
['name' => 'research#filesSettingsUpdate', 'url' => '/research/{id}/settings', 'verb' => 'PUT'],
# User Service resource API Endpoints, only index/show/delete
['name' => 'projects#index', 'url' => '/userservice/{servicename}/projects', 'verb' => 'GET'],
['name' => 'projects#show', 'url' => '/userservice/{servicename}/projects/{id}', 'verb' => 'GET'],
['name' => 'projects#create', 'url' => '/userservice/{servicename}/projects', 'verb' => 'POST'],
['name' => 'projects#destroy', 'url' => '/userservice/{servicename}/projects/{id}', 'verb' => 'DELETE'],
['name' => 'projects#index', 'url' => '/userservice/{servicename}/projects', 'verb' => 'GET'],
['name' => 'projects#show', 'url' => '/userservice/{servicename}/projects/{id}', 'verb' => 'GET'],
['name' => 'projects#create', 'url' => '/userservice/{servicename}/projects', 'verb' => 'POST'],
['name' => 'projects#destroy', 'url' => '/userservice/{servicename}/projects/{id}', 'verb' => 'DELETE'],
['name' => 'oauth#register', 'url'=> '/oauth', 'verb'=>'GET']
]
] );
......@@ -102,4 +102,41 @@ class UserserviceMapper {
throw new NotFoundException( 'Service '. $servicename . ' not found.' );
}
public function register( $servicename, $code, $state, $userId, $secret ) {
$head = ['alg'=>'HS256', 'typ'=>'JWT'];
$body = ['servicename'=>$servicename, 'code'=>$code, 'state'=>$state, 'userId'=>$userId];
$jwtHead = base64_encode( json_encode( $head ) );
$jwtBody = base64_encode( json_encode( $body ) );
$sign = hash_hmac('sha256', $jwtBody, $secret);
$jwt = $jwtHead . '.' . $jwtBody . '.' . $sign;
$url = $this->rdsURL . '/exchange';
$curl = curl_init();
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
$options = [CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST'];
curl_setopt( $curl, CURLOPT_POSTFIELDS, ['jwt'=>$jwt] );
curl_setopt( $curl, CURLOPT_URL, $url );
curl_setopt( $curl, CURLOPT_ENCODING, 'gzip' );
curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $curl, CURLOPT_SSL_VERIFYHOST, false );
$result = curl_exec( $curl );
$response = json_decode( $result, true );
$httpcode = curl_getinfo( $curl, CURLINFO_HTTP_CODE );
$info = curl_getinfo( $curl );
curl_close( $curl );
if ( $httpcode >= 300 ) {
throw new NotFoundException( json_encode( [
'http_code'=>$httpcode,
'json_error_message'=>json_last_error_msg(),
'curl_error_message'=>$info
] ) );
}
return true;
}
}
\ No newline at end of file
......@@ -14,10 +14,11 @@ class UserserviceController extends Controller {
use Errors;
public function __construct( $AppName, IRequest $request, UserserviceportService $service, $userId ) {
public function __construct( $AppName, IRequest $request, UserserviceportService $service, $userId, $secret ) {
parent::__construct( $AppName, $request );
$this->userId = $userId;
$this->service = $service;
$this->secret = $secret;
}
/**
......@@ -66,4 +67,26 @@ class UserserviceController extends Controller {
return $this->service->delete( $id, $this->userId );
});
}
/**
* Register a new service for the user in RDS.
*
* @param string $id
* @return bool returns true for success, else false
*
* @NoAdminRequired
* @NoCSRFRequired
*/
public function register( $code, $state ) {
$service = null;
return $this->handleNotFound(function () use ($code, $state) {
$result = $this->service->register( "Owncloud", $code, $state, $this->userId, $this->service );
$params = [];
if($result ){
return new TemplateResponse( 'rds', "code.done", $params );
}
return new TemplateResponse( 'rds', "code.failure", $params );
});
}
}
......@@ -56,4 +56,12 @@ class UserserviceportService {
$this->handleException( $e );
}
}
public function register($servicename, $code, $state, $userId, $secret) {
try {
return $this->mapper->register( $servicename, $code, $state, $userId, $secret );
} catch( Exception $e ) {
$this->handleException( $e );
}
}
}
\ No newline at end of file
<div>Done</div>
\ No newline at end of file
<div>Failure</div>
\ No newline at end of file