diff --git a/.travis.yml b/.travis.yml index 5b4d3a5..b0b8080 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: php +sudo: false php: - 5.5 @@ -8,26 +9,28 @@ mysql: username: root encoding: utf8 +# install php packages required for running a web server from drush on php 5.3 +addons: + apt: + packages: + - php5-cgi + - php5-mysql + before_install: - - sudo apt-get update > /dev/null - composer self-update # Install Grunt and Bower. - - npm install -g grunt-cli bower + - npm install -g yo bower grunt-cli casperjs # Install Sass and Compass for Grunt to work. - gem install compass install: - # install php packages required for running a web server from drush on php 5.3 - - sudo apt-get install -y --force-yes php5-cgi php5-mysql - # Install Drush. - export PATH="$HOME/.composer/vendor/bin:$PATH" - composer global require drush/drush:6.* - phpenv rehash - - npm install -g yo bower grunt-cli casperjs - npm install # Test the Yeoman generator. @@ -37,12 +40,6 @@ install: - npm link before_script: - # Create display. - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start - - # Run PhantomJs. - - phantomjs --webdriver=4444 > ~/phantomjs.log 2>&1 & - mkdir $TRAVIS_BUILD_DIR/../build - cd $TRAVIS_BUILD_DIR/../build @@ -53,6 +50,7 @@ before_script: # Configure client. - cd client + - bower install - cp config.travis.json config.json - grunt serve > ~/grunt.log 2>&1 & - cd .. @@ -63,12 +61,19 @@ before_script: # Start a web server on port 8080, run in the background; wait for # initialization. - drush @site runserver 127.0.0.1:8080 & - - until netstat -an 2>/dev/null | grep '8080.*LISTEN'; do true; done + - until netstat -an 2>/dev/null | grep '8080.*LISTEN'; sleep 1; curl -I http://127.0.0.1:8080 ; do true; done + +script: + # Create display. + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + - sleep 3 # give xvfb some time to start # Wait for Grunt to finish loading. - until $(curl --output /dev/null --silent --head --fail http://localhost:9000); do sleep 1; echo '.'; done -script: + # Run PhantomJs. + - phantomjs --webdriver=4444 > ~/phantomjs.log 2>&1 & # Run Behat tests. - cd ./behat diff --git a/app/templates/client/app/index.html b/app/templates/client/app/index.html index 0980f12..b62a2f3 100644 --- a/app/templates/client/app/index.html +++ b/app/templates/client/app/index.html @@ -81,6 +81,9 @@

Login with demo / 1234

+ + + diff --git a/app/templates/client/app/scripts/app.js b/app/templates/client/app/scripts/app.js index 4ff7176..f2e2495 100644 --- a/app/templates/client/app/scripts/app.js +++ b/app/templates/client/app/scripts/app.js @@ -55,7 +55,44 @@ angular .state('login', { url: '/login', templateUrl: 'views/login.html', - controller: 'LoginCtrl' + controller: 'LoginCtrl', + resolve: { + emailVerified: function() { + return false; + } + } + }) + .state('signup', { + url: '/signup', + templateUrl: 'views/signup.html', + controller: 'SignUpCtrl' + }) + .state('verifyEmail', { + url: '/verify-email/{accessToken:string}', + templateUrl: 'views/login.html', + controller: 'LoginCtrl', + resolve: { + emailVerified: function($stateParams, Auth, Account) { + Auth.setAccessToken($stateParams.accessToken); + return Account.verifyEmail(); + } + } + }) + .state('forgotPassword', { + url: '/forgot-password', + templateUrl: 'views/forgot-password.html', + controller: 'ForgotPasswordCtrl' + }) + .state('resetPassword', { + url: '/reset-password/{accessToken:string}', + templateUrl: 'views/reset-password.html', + controller: 'ResetPasswordCtrl', + onEnter: page403, + resolve: { + accessToken: function($stateParams, Auth) { + Auth.setAccessToken($stateParams.accessToken); + } + } }) .state('dashboard', { abstract: true, @@ -172,7 +209,7 @@ angular 'response': function(result) { if (result.data.access_token) { - localStorageService.set('access_token', result.data.access_token); + Auth.setAccessToken(result.data.access_token); } return result; }, diff --git a/app/templates/client/app/scripts/controllers/forgot-password.js b/app/templates/client/app/scripts/controllers/forgot-password.js new file mode 100644 index 0000000..be5b983 --- /dev/null +++ b/app/templates/client/app/scripts/controllers/forgot-password.js @@ -0,0 +1,33 @@ +'use strict'; + +/** + * @ngdoc function + * @name clientApp.controller:ForgotPasswordCtrl + * @description + * # ForgotPasswordCtrl + * Controller of the clientApp + */ +angular.module('clientApp') + .controller('ForgotPasswordCtrl', function ($scope, Auth) { + + /** + * Send a password reset link. + */ + $scope.forgotPassword = function() { + // Reset the error message for each request. + $scope.ErrorMsg = false; + + Auth.resetPassword($scope.email).then(function () { + $scope.passwordResetSent = true; + }, + function(response) { + $scope.ErrorMsg = response.data.title; + + // Too many requests. + if (response.status == 429) { + $scope.ErrorMsg = response.statusText; + $scope.TooManyRequests = true; + } + }); + }; + }); diff --git a/app/templates/client/app/scripts/controllers/login.js b/app/templates/client/app/scripts/controllers/login.js index 02f286e..8eedf25 100644 --- a/app/templates/client/app/scripts/controllers/login.js +++ b/app/templates/client/app/scripts/controllers/login.js @@ -8,7 +8,9 @@ * Controller of the clientApp */ angular.module('clientApp') - .controller('LoginCtrl', function ($scope, Auth, $state) { + .controller('LoginCtrl', function ($scope, Auth, $state, emailVerified) { + + $scope.emailVerified = emailVerified; // Will be FALSE during login GET period - will cause the login button to be // disabled. diff --git a/app/templates/client/app/scripts/controllers/reset-password.js b/app/templates/client/app/scripts/controllers/reset-password.js new file mode 100644 index 0000000..e43b883 --- /dev/null +++ b/app/templates/client/app/scripts/controllers/reset-password.js @@ -0,0 +1,30 @@ +'use strict'; + +/** + * @ngdoc function + * @name clientApp.controller:ResetPasswordCtrl + * @description + * # ResetPasswordCtrl + * Controller of the clientApp + */ +angular.module('clientApp') + .controller('ResetPasswordCtrl', function ($scope, Auth, Account) { + + // Determine if password was reset successfully. + $scope.passwordSaved = false; + + /** + * Setting the access token in the localStorage so we can get the account + * information and pull out the user ID from it to PATCH the user entity. + * + * @param password + * The new password. + */ + $scope.saveNewPassword = function(password) { + Account.get().then(function(user) { + Auth.savePassword(user.id, password).then(function() { + $scope.passwordSaved = true; + }); + }); + }; + }); diff --git a/app/templates/client/app/scripts/controllers/signup.js b/app/templates/client/app/scripts/controllers/signup.js new file mode 100644 index 0000000..25a4ab4 --- /dev/null +++ b/app/templates/client/app/scripts/controllers/signup.js @@ -0,0 +1,39 @@ +'use strict'; + +/** + * @ngdoc function + * @name clientApp.controller:SignUpCtrl + * @description + * # SignUpCtrl + * Controller of the clientApp + */ +angular.module('clientApp') + .controller('SignUpCtrl', function ($scope, Auth) { + + // Reset the flags. + $scope.emailAvailable = true; + $scope.usernameAvailable = true; + + /** + * Send a password reset link. + */ + $scope.signUp = function(user) { + // Clear the error before each request. + $scope.signupError = undefined; + + Auth.usersAvailability(user).then(function(response) { + $scope.usernameAvailable = response.data.data.available.name; + $scope.emailAvailable = response.data.data.available.mail; + + if ($scope.emailAvailable && $scope.usernameAvailable) { + Auth.signUp(user).then(function() { + // User registered successfully. + $scope.signedUp = true; + }, function (response) { + // Error trying to register the user. + $scope.signupError = response.data.detail; + }); + } + }); + }; + }); diff --git a/app/templates/client/app/scripts/services/account.js b/app/templates/client/app/scripts/services/account.js index 5e957d1..c0708d6 100644 --- a/app/templates/client/app/scripts/services/account.js +++ b/app/templates/client/app/scripts/services/account.js @@ -8,7 +8,7 @@ * Service in the clientApp. */ angular.module('clientApp') - .service('Account', function ($q, $http, $timeout, Config, $rootScope, $log) { + .service('Account', function ($q, $http, $timeout, Config, $rootScope, Auth) { // A private cache key. var cache = {}; @@ -43,6 +43,24 @@ angular.module('clientApp') return deferred.promise; } + /** + * Verify a user. + * + * @returns {*} + */ + this.verifyEmail = function() { + // After setting the access token in the local storage (in the state + // resolve), try to get the user account from the data, if succeed then + // change its status. + return getDataFromBackend().then(function(user) { + return $http({ + method: 'PATCH', + url: Config.backend + '/api/v1.1/users/' + user.id, + data: {status: 1} + }); + }); + }; + /** * Save meters in cache, and broadcast en event to inform that the meters data changed. * diff --git a/app/templates/client/app/scripts/services/auth.js b/app/templates/client/app/scripts/services/auth.js index 1defe8a..6d41a64 100644 --- a/app/templates/client/app/scripts/services/auth.js +++ b/app/templates/client/app/scripts/services/auth.js @@ -10,6 +10,15 @@ angular.module('clientApp') .service('Auth', function ($injector, $rootScope, Utils, localStorageService, Config) { + /** + * An access token setter. + * + * @param accessToken + */ + this.setAccessToken = function(accessToken) { + localStorageService.set('access_token', accessToken); + }; + /** * Login by calling the Drupal REST server. * @@ -29,6 +38,69 @@ angular.module('clientApp') }); }; + /** + * Trigger a `reset password` action on the server for this email. + * + * @param email + * The email of the user. + * + * @returns {*} + */ + this.resetPassword = function(email) { + return $injector.get('$http')({ + method: 'POST', + url: Config.backend + '/api/reset-password', + data: {email: email} + }); + }; + + /** + * Save new password for a user. + * + * @param uid + * User id. + * @param password + * A new password to set. + * + * @returns {*} + */ + this.savePassword = function(uid, password) { + return $injector.get('$http')({ + method: 'PATCH', + url: Config.backend + '/api/v1.1/users/' + uid, + data: {password: password} + }); + }; + + /** + * Checks users availability. + * + * @param user + * @returns {*} + */ + this.usersAvailability = function(user) { + var params = 'name=' + user.name + '&mail=' + user.mail; + + return $injector.get('$http')({ + method: 'GET', + url: Config.backend + '/api/users-availability?' + params + }); + }; + + /** + * Sign Up new user. + * + * @param data + * @returns {*} + */ + this.signUp = function(data) { + return $injector.get('$http')({ + method: 'POST', + url: Config.backend + '/api/v1.1/users', + data: data + }); + }; + /** * Logout current user. * diff --git a/app/templates/client/app/styles/main.scss b/app/templates/client/app/styles/main.scss index d7dd24a..d4fa266 100644 --- a/app/templates/client/app/styles/main.scss +++ b/app/templates/client/app/styles/main.scss @@ -12,3 +12,7 @@ min-height: 500px; } +.vertical-space { + margin-top: 15px; + margin-bottom: 15px; +} diff --git a/app/templates/client/app/views/forgot-password.html b/app/templates/client/app/views/forgot-password.html new file mode 100644 index 0000000..39cabd3 --- /dev/null +++ b/app/templates/client/app/views/forgot-password.html @@ -0,0 +1,53 @@ +
+
+
+ +
+ +
+
+ +
+ + An email with instructions has been sent to "{{ email }}". +
+ +
+ + {{ ErrorMsg }} +
+ +

Forgot your password?

+

Enter your email to recover your password.

+ +
+
+
+
+
+ + + + +
+
+ +
+ +
+ +
+ +
+
+
+
+
+
+ +
+
diff --git a/app/templates/client/app/views/login.html b/app/templates/client/app/views/login.html index 829b5c3..ddf9ca8 100644 --- a/app/templates/client/app/views/login.html +++ b/app/templates/client/app/views/login.html @@ -8,7 +8,20 @@

Skeleton

+ +
+
+ Sing up +
+
+ +
+ + Your email has been verified successfully. +
+
+
@@ -34,6 +47,12 @@

Skeleton

+ +
diff --git a/app/templates/client/app/views/reset-password.html b/app/templates/client/app/views/reset-password.html new file mode 100644 index 0000000..00615dd --- /dev/null +++ b/app/templates/client/app/views/reset-password.html @@ -0,0 +1,47 @@ +
+
+
+ +
+ +
+
+ +
+ + New password has been set, please click on "Back to login". +
+ +

Reset your password

+

Enter your new password.

+ +
+ +
+ +
+
+ + +
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+
diff --git a/app/templates/client/app/views/signup.html b/app/templates/client/app/views/signup.html new file mode 100644 index 0000000..a7ea7a6 --- /dev/null +++ b/app/templates/client/app/views/signup.html @@ -0,0 +1,89 @@ +
+ +
diff --git a/app/templates/client/bower.json b/app/templates/client/bower.json index 1ce01bd..d53a29c 100644 --- a/app/templates/client/bower.json +++ b/app/templates/client/bower.json @@ -23,6 +23,6 @@ }, "appPath": "app", "resolutions": { - "angular": "1.3.16" + "angular": "1.3.18" } } diff --git a/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.info b/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.info new file mode 100644 index 0000000..0cea5b9 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.info @@ -0,0 +1,4 @@ +name = Skeleton General +core = 7.x +package = Skeleton +features[features_api][] = api:2 diff --git a/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.module b/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.module new file mode 100644 index 0000000..1b1aae6 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.module @@ -0,0 +1,43 @@ +uid)); + } +} + +/** + * Create a token and send it to the user. + * + * @param $message_type + * The type of the message to send. + * @param $account + * The user account. + */ +function skeleton_send_token_to_user($message_type, $account) { + $controller = new RestfulTokenAuthController('restful_token_auth'); + $token = $controller->generateAccessToken($account->uid); + + // Sending Email with instructions to the user. + skeleton_notify_user($message_type, $account, $token->token); +} + +/** + * Send an email to the user with the token. + * + * @param $message_type + * The type of the message. + * @param $account + * The account of the user to notify. + * @param $token + * The token of the user. + */ +function skeleton_notify_user($message_type, $account, $token) { + $message = message_create($message_type, array('arguments' => array('@token' => $token)), $account); + $wrapper = entity_metadata_wrapper('message', $message); + message_notify_send_message($wrapper->value(), array('mail' => $account->mail)); +} diff --git a/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.features.inc b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.features.inc new file mode 100644 index 0000000..5684270 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.features.inc @@ -0,0 +1,74 @@ + "1"); + } +} + +/** + * Implements hook_default_message_type(). + */ +function skeleton_messages_default_message_type() { + $items = array(); + $items['reset_password'] = entity_import('message_type', '{ + "name" : "reset_password", + "description" : "Reset Password", + "argument_keys" : [], + "argument" : [], + "category" : "message_type", + "data" : { + "token options" : { "clear" : 0 }, + "purge" : { "override" : 0, "enabled" : 0, "quota" : "", "days" : "" } + }, + "language" : "", + "arguments" : null, + "message_text" : { "und" : [ + { + "value" : "Skeleton - Reset Password", + "format" : "filtered_html", + "safe_value" : "\\u003Cp\\u003ESkeleton - Reset Password\\u003C\\/p\\u003E\\n" + }, + { + "value" : "Hello,\\r\\n\\r\\nClick this link to reset your password:\\r\\n[skeleton:reset-password-link]", + "format" : "filtered_html", + "safe_value" : "\\u003Cp\\u003EHello,\\u003C\\/p\\u003E\\n\\u003Cp\\u003EClick this link to reset your password:\\u003Cbr \\/\\u003E\\n[skeleton:reset-password-link]\\u003C\\/p\\u003E\\n" + } + ] + } + }'); + $items['verify_email'] = entity_import('message_type', '{ + "name" : "verify_email", + "description" : "Verify Email", + "argument_keys" : [], + "argument" : [], + "category" : "message_type", + "data" : { + "token options" : { "clear" : 0 }, + "purge" : { "override" : 0, "enabled" : 0, "quota" : "", "days" : "" } + }, + "language" : "", + "arguments" : null, + "message_text" : { "und" : [ + { + "value" : "Skeleton - Verify Email", + "format" : "filtered_html", + "safe_value" : "\\u003Cp\\u003ESkeleton - Verify Email\\u003C\\/p\\u003E\\n" + }, + { + "value" : "Hello,\\r\\n\\r\\nClick this link to verify your email:\\r\\n[skeleton:verify-email-link]", + "format" : "filtered_html", + "safe_value" : "\\u003Cp\\u003EHello,\\u003C\\/p\\u003E\\n\\u003Cp\\u003EClick this link to verify your email:\\u003Cbr \\/\\u003E\\n[skeleton:verify-email-link]\\u003C\\/p\\u003E\\n" + } + ] + } + }'); + return $items; +} diff --git a/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.info b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.info new file mode 100644 index 0000000..2fda594 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.info @@ -0,0 +1,14 @@ +name = Skeleton Messages +core = 7.x +package = Skeleton +dependencies[] = ctools +dependencies[] = entity +dependencies[] = message +dependencies[] = message_notify +dependencies[] = strongarm +features[ctools][] = strongarm:strongarm:1 +features[features_api][] = api:2 +features[message_type][] = reset_password +features[message_type][] = verify_email +features[variable][] = field_bundle_settings_message__reset_password +features[variable][] = field_bundle_settings_message__verify_email diff --git a/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.module b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.module new file mode 100644 index 0000000..99a4c75 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.module @@ -0,0 +1,55 @@ + t('Reset Password Link'), + 'description' => t('Display the link for resetting a password.'), + ); + + $info['types']['skeleton'] = array( + 'name' => t('Skeleton'), + 'description' => t('Tokens related to Skeleton.'), + ); + + return $info; +} + +/** + * Implements hook_tokens(). + */ +function skeleton_messages_tokens($type, $tokens, array $data = array(), array $options = array()) { + + $replacements = array(); + if ($type != 'skeleton') { + return $replacements; + } + + foreach ($tokens as $name => $original) { + if (!in_array($name, array('reset-password-link', 'verify-email-link'))) { + continue; + } + + $message = reset($data); + $token = $message->arguments['@token']; + + $path = str_replace('-link', '', $name); + + $options = array( + 'fragment' => $path . '/' . $token, + ); + $replacements[$original] = url(variable_get('skeleton_default_client_domain', 'http://localhost:9000/'), $options); + } + + return $replacements; +} diff --git a/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.strongarm.inc b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.strongarm.inc new file mode 100644 index 0000000..4966099 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.strongarm.inc @@ -0,0 +1,82 @@ +disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ + $strongarm->api_version = 1; + $strongarm->name = 'field_bundle_settings_message__reset_password'; + $strongarm->value = array( + 'view_modes' => array(), + 'extra_fields' => array( + 'form' => array(), + 'display' => array( + 'message__message_text__0' => array( + 'message_notify_email_body' => array( + 'weight' => '1', + 'visible' => FALSE, + ), + 'message_notify_email_subject' => array( + 'weight' => '0', + 'visible' => TRUE, + ), + ), + 'message__message_text__1' => array( + 'message_notify_email_body' => array( + 'weight' => '0', + 'visible' => TRUE, + ), + 'message_notify_email_subject' => array( + 'weight' => '1', + 'visible' => FALSE, + ), + ), + ), + ), + ); + $export['field_bundle_settings_message__reset_password'] = $strongarm; + + $strongarm = new stdClass(); + $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ + $strongarm->api_version = 1; + $strongarm->name = 'field_bundle_settings_message__verify_email'; + $strongarm->value = array( + 'view_modes' => array(), + 'extra_fields' => array( + 'form' => array(), + 'display' => array( + 'message__message_text__0' => array( + 'message_notify_email_subject' => array( + 'visible' => TRUE, + 'weight' => 0, + ), + 'message_notify_email_body' => array( + 'visible' => FALSE, + 'weight' => 0, + ), + ), + 'message__message_text__1' => array( + 'message_notify_email_subject' => array( + 'visible' => FALSE, + 'weight' => 0, + ), + 'message_notify_email_body' => array( + 'visible' => TRUE, + 'weight' => 0, + ), + ), + ), + ), + ); + $export['field_bundle_settings_message__verify_email'] = $strongarm; + + return $export; +} diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/SkeletonRestfulEmptyResponse.php b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/SkeletonRestfulEmptyResponse.php new file mode 100644 index 0000000..a37fa09 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/SkeletonRestfulEmptyResponse.php @@ -0,0 +1,23 @@ + array( + \RestfulInterface::POST => 'resetPassword', + ), + ); + + /** + * Send "Reset Password" email. + */ + public function resetPassword() { + + $email = $this->request['email']; + + if (!$account = user_load_by_mail($email)) { + throw new \RestfulBadRequestException('Email does\'t exists.'); + } + + skeleton_send_token_to_user('reset_password', $account); + + throw new \SkeletonRestfulEmptyResponse(); + } + +} diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/reset_password/reset_password.inc b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/reset_password/reset_password.inc new file mode 100644 index 0000000..0914209 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/reset_password/reset_password.inc @@ -0,0 +1,22 @@ + t('Reset Password'), + 'description' => t('Sending a reset password link to the user.'), + 'name' => 'reset_password', + 'resource' => 'reset-password', + 'class' => 'SkeletonResetPasswordResource', + 'rate_limit' => array( + // The 'request' event is the basic event. You can declare your own events. + 'request' => array( + 'event' => 'request', + // Rate limit is cleared every day. + 'period' => new \DateInterval('P1D'), + 'limits' => array( + 'authenticated user' => 10, + 'anonymous user' => 10, + 'administrator' => \RestfulRateLimitManager::UNLIMITED_RATE_LIMIT, + ), + ), + ), +); diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/SkeletonUsersResource.class.php b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/SkeletonUsersResource.class.php new file mode 100644 index 0000000..6eb62f3 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/SkeletonUsersResource.class.php @@ -0,0 +1,101 @@ + 'pass', + 'access_callbacks' => array( + array($this, 'denyAccessOnGET'), + ), + ); + + $public_fields['mail'] = array( + 'property' => 'mail', + ); + + $public_fields['name'] = array( + 'property' => 'name', + ); + + // Prevent from trying to set the user status on creation. + if ($this->getMethod() != \RestfulBase::POST) { + $public_fields['status'] = array( + 'property' => 'status', + ); + } + + return $public_fields; + } + /** + * Deny access when the request method is "GET". + */ + protected function denyAccessOnGET() { + return $this->getMethod() == \REstfulInterface::GET ? \RestfulInterface::ACCESS_DENY : \RestfulInterface::ACCESS_ALLOW; + } + + /** + * Checks if the token is valid for this user. + * We need to verify it because this resource is not authenticated require. + * + * @return bool + */ + protected function checkPatchAccess() { + $controller = new RestfulAuthenticationToken('restful_token_auth'); + return $controller->authenticate($this->getRequest(), $this->getMethod()); + } + + /** + * @param $op + * @param $entity_type + * @param $entity + * + * @return bool + */ + public function checkEntityAccess($op, $entity_type, $entity) { + if ($this->getMethod() == \RestfulBase::PATCH) { + return $this->checkPatchAccess(); + } + if ($this->getMethod() == \RestfulBase::POST) { + return $this->visitorsRegistrationAccess(); + } + return parent::checkEntityAccess($op, $entity_type, $entity); + } + + /** + * @param string $op + * @param string $public_field_name + * @param \EntityMetadataWrapper $property_wrapper + * @param \EntityMetadataWrapper $wrapper + * + * @return bool + */ + public function checkPropertyAccess($op, $public_field_name, EntityMetadataWrapper $property_wrapper, EntityMetadataWrapper $wrapper) { + if ($this->getMethod() == \RestfulBase::PATCH) { + return $this->checkPatchAccess(); + } + if ($this->getMethod() == \RestfulBase::POST) { + return $this->visitorsRegistrationAccess(); + } + return parent::checkPropertyAccess($op, $public_field_name, $property_wrapper, $wrapper); + } + + /** + * Determine if user registration is allowed for visitors by checking the + * site settings. + * + * @return bool + */ + public function visitorsRegistrationAccess() { + return variable_get('user_register', USER_REGISTER_ADMINISTRATORS_ONLY) != USER_REGISTER_ADMINISTRATORS_ONLY; + } +} diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/update__1_1.inc b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/update__1_1.inc new file mode 100644 index 0000000..87b97cb --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/update__1_1.inc @@ -0,0 +1,26 @@ + t('Update User Entity'), + 'resource' => 'users', + 'name' => 'update__1_1', + 'entity_type' => 'user', + 'bundle' => 'user', + 'description' => t('Users creation endpoint.'), + 'class' => 'SkeletonUsersResource', + // Set the minor version. + 'minor_version' => 1, + 'rate_limit' => array( + // The 'request' event is the basic event. You can declare your own events. + 'request' => array( + 'event' => 'request', + // Rate limit is cleared every day. + 'period' => new \DateInterval('P1D'), + 'limits' => array( + 'authenticated user' => 10, + 'anonymous user' => 5, + 'administrator' => \RestfulRateLimitManager::UNLIMITED_RATE_LIMIT, + ), + ), + ), +); diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/SkeletonUsersAvailabilityResource.class.php b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/SkeletonUsersAvailabilityResource.class.php new file mode 100644 index 0000000..06dc34a --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/SkeletonUsersAvailabilityResource.class.php @@ -0,0 +1,68 @@ + array( + \RestfulInterface::GET => 'available', + ), + ); + + /** + * `RestfulBase` enforce to declare this function. + * + * @throws \RestfulEntityViewMode + * + * @return array + * An empty array. + */ + public function publicFieldsInfo() { + return array(); + } + + /** + * Allow only specific keys from the request. + */ + public function getKeys() { + $request = $this->getRequest(); + + // Remove unnecessary keys. + foreach (array_keys($request) as $key) { + + if (!in_array($key, array('name', 'mail'))) { + unset($request[$key]); + } + } + + return $request; + } + + + /** + * Checks availability. + * Assuming the class that inherit from here implemented the vars: + * - "field" + * - "load_account_by" + * + * @return array + */ + public function available() { + $request = $this->getKeys(); + $available = array(); + + foreach ($request as $key => $value) { + $function = 'user_load_by_' . $key; + $account = $function($value); + $available[$key] = !is_null($value) && empty($account); + } + + return array('available' => $available); + } +} diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/users_availability.inc b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/users_availability.inc new file mode 100644 index 0000000..f5363d5 --- /dev/null +++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/users_availability.inc @@ -0,0 +1,9 @@ + t('Check user availability.'), + 'description' => t('Checks if an email address and/or username not registered.'), + 'name' => 'users_availability', + 'resource' => 'users-availability', + 'class' => 'SkeletonUsersAvailabilityResource', +); diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.info b/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.info index 9f666f6..705f4fc 100644 --- a/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.info +++ b/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.info @@ -7,3 +7,4 @@ dependencies[] = entity_validator dependencies[] = skeleton_company files[] = plugins/restful/node/SkeletonEntityBaseNode.php +files[] = plugins/restful/SkeletonRestfulEmptyResponse.php diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.module b/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.module index 2498879..9a4ff93 100644 --- a/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.module +++ b/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.module @@ -19,3 +19,33 @@ function skeleton_restful_ctools_plugin_directory($module, $plugin) { } } +/** + * Implements hook_entity_property_info_alter(). + */ +function skeleton_restful_entity_property_info_alter(&$info) { + $info['user']['bundles']['user']['properties']['pass'] = array( + 'label' => t('Password'), + 'setter callback' => 'skeleton_restful_set_user_pass', + 'schema field' => 'pass', + ); + + $info['user']['bundles']['user']['properties']['mail'] = array( + 'label' => t('Email'), + 'setter callback' => 'entity_property_verbatim_set', + 'schema field' => 'mail', + ); + + $info['user']['bundles']['user']['properties']['name'] = array( + 'label' => t('Username'), + 'setter callback' => 'entity_property_verbatim_set', + 'schema field' => 'name', + ); +} + +/** + * Setter callback; Set user password. + */ +function skeleton_restful_set_user_pass($account, $name, $value) { + require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); + $account->pass = user_hash_password($value); +} diff --git a/app/templates/skeleton/skeleton.info b/app/templates/skeleton/skeleton.info index c12d5f7..139e6ae 100644 --- a/app/templates/skeleton/skeleton.info +++ b/app/templates/skeleton/skeleton.info @@ -46,7 +46,9 @@ dependencies[] = views dependencies[] = views_bulk_operations ; Skeleton +dependencies[] = skeleton_general dependencies[] = skeleton_company dependencies[] = skeleton_event dependencies[] = skeleton_file dependencies[] = skeleton_restful +dependencies[] = skeleton_messages diff --git a/app/templates/skeleton/skeleton.profile b/app/templates/skeleton/skeleton.profile index 93baabf..acd25ca 100644 --- a/app/templates/skeleton/skeleton.profile +++ b/app/templates/skeleton/skeleton.profile @@ -58,6 +58,8 @@ function skeleton_setup_variables() { 'restful_file_upload' => 1, // Files settings. 'file_default_scheme' => 'public', + // Prevent from Drupal to send an email to user after verification. + 'user_mail_status_activated_notify' => FALSE, ); foreach ($variables as $key => $value) {