Skip to content

Commit

Permalink
Merge branch 'master' of github.com:spira/spira into feature/ide-helper
Browse files Browse the repository at this point in the history
* 'master' of github.com:spira/spira: (26 commits)
  Reconfigured forum redirect in the frontend to use /forum
  Added forum redirect
  Made hostname for the virtual hosts configurable by the environment files
  Implemented forum redirect via sso auth route closes #160
  Updating front and backend unit tests to increase coverage.
  Update profile.tpl.html
  Updated profile template, now dynamically adding login providers. Updating social login functions and tests.
  Resolved qa issue with the postgres database trying to connect to the wrong database name.
  Restored .secret.env linking to the php container
  Removing dead route. Removing tests which are no longer required. Updating some user tests to use new createUser function.
  Applying style patch.
  Writing unit tests for auth service. Skipping tests for unlink social login function in UserControlller as this will be be replaced in the near future.
  Set client id for user after method tested to cover case when the user doesn't have a client id defined
  Fixed SSO test that was failing for HHVM as hash_hmac is not allowed to have a null secret for HHVM
  Switched to phantomjs 2.0.0 tag for the docker dev tools container
  Adding login and profile unit tests for social logins.
  Fixed indentation
  Added override for docker compose build file to allow the ci service to substitute the build directory
  Adding in functionality to unlink social login for user.
  Moving social login code to authService. In User Controller now returning false if user credentails/social logins are empty.
  ...
  • Loading branch information
zakhenry committed Aug 18, 2015
2 parents 712c522 + 22f17ee commit cbf9eb7
Show file tree
Hide file tree
Showing 32 changed files with 677 additions and 176 deletions.
2 changes: 1 addition & 1 deletion api/.env
Expand Up @@ -16,7 +16,7 @@ AUTH_MODEL=App\Models\User
DB_CONNECTION=pgsql
DB_HOST={$DATABASE_PORT_5432_TCP_ADDR}
DB_PORT=5432
DB_DATABASE=spira
DB_DATABASE={$POSTGRES_DB}
DB_USERNAME={$POSTGRES_USER}
DB_PASSWORD={$POSTGRES_PASSWORD}

Expand Down
66 changes: 59 additions & 7 deletions api/app/Http/Controllers/UserController.php
Expand Up @@ -3,6 +3,7 @@
use App;
use App\Extensions\Lock\Manager;
use App\Http\Transformers\EloquentModelTransformer;
use App\Models\SocialLogin;
use App\Models\User;
use App\Models\UserProfile;
use Illuminate\Database\Eloquent\ModelNotFoundException;
Expand Down Expand Up @@ -175,6 +176,14 @@ public function patchOne($id, Request $request)
$model->setProfile($profile);
}

// Extract the credentials and update if necessary
$credentialUpdateDetails = $request->get('_user_credential', []);
if (!empty($credentialUpdateDetails)) {
$credentials = UserCredential::findOrNew($id);
$credentials->fill($credentialUpdateDetails);
$model->setCredential($credentials);
}

$jwtAuth = App::make('Tymon\JWTAuth\JWTAuth');

$token = $jwtAuth->fromUser($model);
Expand Down Expand Up @@ -203,23 +212,66 @@ public function resetPassword($email)
}

/**
* Get the user's profile.
* Deletes a social login entry from the database
*
* @param $id
* @return \Illuminate\Http\Response
* @param $provider
* @return \Spira\Responder\Response\ApiResponse
*/
public function unlinkSocialLogin($id, $provider)
{
if (!$socialLogin = SocialLogin::where('user_id', '=', $id)
->where('provider', '=', $provider)
->first()) {
throw new NotFoundHttpException('Sorry, this provider does not exist for this user.');
}

$socialLogin->delete();

$jwtAuth = App::make('Tymon\JWTAuth\JWTAuth');

$token = $jwtAuth->fromUser($this->repository->find($id));

return $this->getResponse()->header('Authorization-Update', $token)->noContent();
}

/**
* Get full user details
*
* @param string $id
* @return \Spira\Responder\Response\ApiResponse
*/
public function getProfile($id)
public function getOne($id)
{
$this->validateId($id, $this->getKeyName());

$userProfile = UserProfile::find($id);
$user = User::find($id);

$userData = $user->toArray();

if (is_null($user->userCredential)) {
$userData['_user_credential'] = false;
} else {
$userData['_user_credential'] = $user->userCredential->toArray();
}

if (is_null($user->socialLogins)) {
$userData['_social_logins'] = false;
} else {
$userData['_social_logins'] = $user->socialLogins->toArray();
}

if (is_null($userProfile)) {
return $this->getResponse()->noContent();
if (is_null($user->userProfile)) {
$newProfile = new UserProfile;
$newProfile->user_id = $id;
$user->setProfile($newProfile);
$userData['_user_profile'] = $newProfile->toArray();
} else {
$userData['_user_profile'] = $user->userProfile->toArray();
}

return $this->getResponse()
->transformer($this->transformer)
->item($userProfile);
->item($userData);
}
}
4 changes: 2 additions & 2 deletions api/app/Http/routes.php
Expand Up @@ -22,12 +22,12 @@

$app->group(['prefix' => 'users', 'namespace' => 'App\Http\Controllers'], function (Application $app) {
$app->get('/', ['uses' => 'UserController@getAll', 'as' => App\Models\User::class]);
$app->get('{id}/profile', ['uses' => 'UserController@getProfile']);
$app->get('{id}', ['uses' => 'UserController@getOne', 'as' => App\Models\User::class]);
$app->put('{id}', ['uses' => 'UserController@putOne']);
$app->patch('{id}', ['uses' => 'UserController@patchOne']);
$app->delete('{id}', ['uses' => 'UserController@deleteOne']);
$app->delete('{id}/password', ['uses' => 'UserController@resetPassword']);
$app->delete('{email}/password', ['uses' => 'UserController@resetPassword']);
$app->delete('{id}/socialLogin/{provider}', ['uses' => 'UserController@unlinkSocialLogin']);
});


Expand Down
25 changes: 15 additions & 10 deletions api/tests/Services/SingleSignOnTest.php
Expand Up @@ -274,21 +274,26 @@ public function testSsoString()
{
$user = [
'username' => null,
'avatar_img_url' => null
'avatar_img_url' => null,
];

$requester = Mockery::mock(VanillaSingleSignOn::class);
$response = $requester->ssoString($user);
$data = explode(' ', $response);
$callback = 'categories';
$request = $this->mockRequest(false, false, false, $callback);

$ssoClass = Mockery::mock(VanillaSingleSignOn::class, [$request, $user]);

$ssoString = $ssoClass->ssoString($user);
$ssoStringPieces = explode(' ', $ssoString);

$string = $ssoStringPieces[0];
$hash = $ssoStringPieces[1];
$timestamp = $ssoStringPieces[2];
$algo = $ssoStringPieces[3];

$string = $data[0];
$hash = $data[1];
$timestamp = $data[2];
$algo = $data[3];
$user['client_id'] = null;
$user['client_id'] = env('VANILLA_JSCONNECT_CLIENT_ID');

$this->assertEquals(base64_encode(json_encode($user)), $string);
$this->assertEquals(hash_hmac('sha1', "$string $timestamp", null), $hash);
$this->assertEquals(hash_hmac('sha1', "$string $timestamp", env('VANILLA_JSCONNECT_SECRET')), $hash);
$this->assertEquals('hmacsha1', $algo);
}
}
97 changes: 67 additions & 30 deletions api/tests/integration/UserTest.php
Expand Up @@ -95,21 +95,6 @@ public function testGetOneBySelfUser()
$this->assertJsonArray();
}

public function testGetProfileByAdminUser()
{
$user = $this->createUser();
$userToGet = $this->createUser(['user_type' => 'guest']);
$token = $this->tokenFromUser($user);

$this->get('/users/'.$userToGet->user_id.'/profile', [
'HTTP_AUTHORIZATION' => 'Bearer '.$token
]);

$this->assertResponseOk();
$this->shouldReturnJson();
$this->assertJsonArray();
}

public function testGetProfileByGuestUser()
{
$this->markTestSkipped('Permissions have not been implemented properly yet.');
Expand All @@ -125,21 +110,6 @@ public function testGetProfileByGuestUser()
$this->assertException('Denied', 403, 'ForbiddenException');
}

public function testGetProfileBySelfUser()
{
$user = $this->createUser(['user_type' => 'guest']);
$userToGet = $user;
$token = $this->tokenFromUser($user);

$this->get('/users/'.$userToGet->user_id.'/profile', [
'HTTP_AUTHORIZATION' => 'Bearer '.$token
]);

$this->assertResponseOk();
$this->shouldReturnJson();
$this->assertJsonArray();
}

public function testPutOne()
{
$factory = $this->app->make('App\Services\ModelFactory');
Expand Down Expand Up @@ -287,6 +257,52 @@ public function testPatchOneByAdminUser()
$this->assertEquals('1221-05-14', $updatedProfile->dob->toDateString());
}

public function testPatchOneByAdminUserPassword()
{
$user = $this->createUser(['user_type' => 'admin']);
$userToUpdate = $this->createUser(['user_type' => 'guest']);
$token = $this->tokenFromUser($user);

$update = [
'_userCredential' => [
'password' => 'foobarfoobar'
]
];

$this->patch('/users/'.$userToUpdate->user_id, $update, [
'HTTP_AUTHORIZATION' => 'Bearer '.$token
]);

$updatedCredentials = UserCredential::find($userToUpdate->user_id);

$this->assertResponseStatus(204);
$this->assertResponseHasNoContent();
$this->assertTrue(Hash::check('foobarfoobar', $updatedCredentials->password));
}

public function testPatchOneBySelfUserPassword()
{
$user = $this->createUser(['user_type' => 'guest']);
$userToUpdate = $user;
$token = $this->tokenFromUser($user);

$update = [
'_userCredential' => [
'password' => 'foobarfoobar'
]
];

$this->patch('/users/'.$userToUpdate->user_id, $update, [
'HTTP_AUTHORIZATION' => 'Bearer '.$token
]);

$updatedCredentials = UserCredential::find($userToUpdate->user_id);

$this->assertResponseStatus(204);
$this->assertResponseHasNoContent();
$this->assertTrue(Hash::check('foobarfoobar', $updatedCredentials->password));
}

public function testPatchOneByGuestUser()
{
$user = $this->createUser(['user_type' => 'guest']);
Expand Down Expand Up @@ -444,6 +460,19 @@ public function testResetPasswordMail()
$this->assertException('invalid', 401, 'UnauthorizedException');
}

public function testResetPasswordMailInvalidEmail()
{
$this->clearMessages();
$user = $this->createUser(['user_type' => 'guest']);
$token = $this->tokenFromUser($user);

$this->delete('/users/foo.bar.' . $user->email . '/password', [], [
'HTTP_AUTHORIZATION' => 'Bearer '.$token
]);

$this->assertResponseStatus(404);
}

public function testChangeEmail()
{
$this->clearMessages();
Expand Down Expand Up @@ -520,4 +549,12 @@ public function testUpdateEmailConfirmedInvalidToken()
]);
$this->assertResponseStatus(422);
}


public function testUnlinkSocialLogin()
{
$this->markTestSkipped(
'This function will be replaced in the near future.'
);
}
}
3 changes: 2 additions & 1 deletion app/bower.json
Expand Up @@ -36,7 +36,8 @@
"angular-http-progress": "~0.1.3",
"angular-jwt-auth": "~3.3.1",
"moment-timezone": "~0.4.0",
"ngInfiniteScroll": "~1.2.1"
"ngInfiniteScroll": "~1.2.1",
"angular-ui-validate": "~1.1.1"
},
"devDependencies": {
"angular-mocks": "~1.4",
Expand Down
5 changes: 1 addition & 4 deletions app/src/app/guest/forum/forum.ts
Expand Up @@ -12,10 +12,7 @@ namespace app.guest.forum {
resolve: /*@ngInject*/{
redirect: ($window:ng.IWindowService, $location:ng.ILocationService) => {

let protocol = $location.protocol(),
host = $location.host().replace(/local(\.app)?/, 'local.forum');

$window.location.href = `${protocol}://${host}`;
$window.location.href = `${$location.protocol()}://${$location.host()}/forum/sso`;
}
},
data: {
Expand Down

0 comments on commit cbf9eb7

Please sign in to comment.