diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php
index 0c3002c..aa86e3b 100644
--- a/core/modules/user/user.api.php
+++ b/core/modules/user/user.api.php
@@ -363,6 +363,52 @@ function hook_user_view($account, $view_mode, $langcode) {
 }
 
 /**
+ * Controls access to a user.
+ *
+ * Modules may implement this hook if they want to have a say in whether or not
+ * a given user has access to perform a given operation on another user.
+ *
+ * The administrative account (user ID #1) always passes any access check,
+ * so this hook is not called in that case. Users with the "administer users"
+ * permission may always view and edit content through the administrative
+ * interface.
+ *
+ * Note that not all modules will want to influence access on all
+ * user types. If your module does not want to actively grant or
+ * block access, return USER_ACCESS_IGNORE or simply return nothing.
+ * Blindly returning FALSE will break other user access modules.
+ *
+ * Also note that this function isn't called for user listings (e.g., RSS feeds,
+ * the default home page at path 'user', a recent content block, etc.)
+ *
+ * @param string $op
+ *   The operation to be performed. Possible values:
+ *   - "view"
+ *   - "view_profile"
+ *   - "cancel"
+ *   - "update"
+ *   Additional operations may be used as desired.
+ * @param Drupal\user\Node|string $access_account
+ *   Either a user entity on which to perform the access check.
+ * @param object $account
+ *   The user object to perform the access check operation on.
+ *
+ * @return string
+ *   - USER_ACCESS_ALLOW: if the operation is to be allowed.
+ *   - USER_ACCESS_DENY: if the operation is to be denied.
+ *   - USER_ACCESS_IGNORE: to not affect this operation at all.
+ *
+ * @ingroup user_access
+ */
+function hook_user_access($op, $access_account, $account, $langcode) {
+  if ($op == 'update' && in_array('student', $access_account->roles)) {
+    return in_array('teacher', $account->roles) ? USER_ACCESS_ALLOW : USER_ACCESS_DENY;
+  }
+  // Returning nothing from this function would have the same effect.
+  return USER_ACCESS_IGNORE;
+}
+
+/**
  * The user was built; the module may modify the structured content.
  *
  * This hook is called after the content has been assembled in a structured array
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 6911853..f932ac6 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -38,6 +38,30 @@
 const USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL = 'visitors_admin_approval';
 
 /**
+ * Denotes that access is allowed for an account.
+ *
+ * Modules should return this value from hook_user_access() to allow access to
+ * an account.
+ */
+const USER_ACCESS_ALLOW = 'allow';
+
+/**
+ * Denotes that access is denied for an account.
+ *
+ * Modules should return this value from hook_user_access() to deny access to an
+ * account.
+ */
+const USER_ACCESS_DENY = 'deny';
+
+/**
+ * Denotes that access is unaffected for a user.
+ *
+ * Modules should return this value from hook_user_access() to indicate no
+ * effect on user access.
+ */
+const USER_ACCESS_IGNORE = NULL;
+
+/**
  * Implement hook_help().
  */
 function user_help($path, $arg) {
@@ -496,6 +520,75 @@ function user_access($string, $account = NULL) {
 }
 
 /**
+ * Determine whether the user has can access users.
+ */
+function user_access_user($op, $access_account, $account = NULL) {
+  $rights = &drupal_static(__FUNCTION__, array());
+
+  // If no user object is supplied, the access check is for the current user.
+  if (empty($account)) {
+    $account = $GLOBALS['user'];
+  }
+
+  // If we've already checked access for this account, user and op, return from
+  // cache.
+  if (isset($rights[$account->uid][$access_account->uid][$op])) {
+    return $rights[$account->uid][$access_account->uid][$op];
+  }
+
+  // Annoymous user can be "viewed" (as username) but not edited/deleted/etc.
+  if (!$access_account->uid) {
+    $rights[$account->uid][$access_account->uid][$op] = $op == 'view' && user_access('access users', $account);
+    return $rights[$account->uid][$access_account->uid][$op];
+  }
+  if (user_access('administer users', $account)) {
+    $rights[$account->uid][$access_account->uid][$op] = TRUE;
+    return TRUE;
+  }
+  if (!user_access('access users', $account) && $access_account->uid != $account->uid) {
+    $rights[$account->uid][$access_account->uid][$op] = FALSE;
+    return FALSE;
+  }
+
+  // We grant access to the user if both of the following conditions are met:
+  // - No modules say to deny access.
+  // - At least one module says to grant access.
+  // If no module specified either allow or deny, we fall back to the
+  // node_access table.
+  $access = module_invoke_all('user_access', $op, $access_account, $account);
+  if (in_array(USER_ACCESS_DENY, $access, TRUE)) {
+    $rights[$account->uid][$access_account->uid][$op] = FALSE;
+    return FALSE;
+  }
+  elseif (in_array(USER_ACCESS_ALLOW, $access, TRUE)) {
+    $rights[$account->uid][$access_account->uid][$op] = TRUE;
+    return TRUE;
+  }
+
+  if ($op == 'view_profile') {
+    $rights[$account->uid][$access_account->uid][$op] = user_access('access user profiles', $account) || $account->uid == $access_account->uid;
+    return $rights[$account->uid][$access_account->uid][$op];
+  }
+  elseif ($op == 'cancel') {
+    $rights[$account->uid][$access_account->uid][$op] = $GLOBALS['user']->uid == $account->uid && user_access('cancel account');
+    return $rights[$account->uid][$access_account->uid][$op];
+  }
+  elseif ($op == 'update') {
+    $rights[$account->uid][$access_account->uid][$op] = $GLOBALS['user']->uid == $account->uid;
+    return $rights[$account->uid][$access_account->uid][$op];
+  }
+
+  // By default allow view as users has access users permission and no one has denied.
+  if ($op == 'view') {
+    $rights[$account->uid][$access_account->uid][$op] = TRUE;
+    return TRUE;
+  }
+  // All non-view operations default to denied.
+  $rights[$account->uid][$access_account->uid][$op] = FALSE;
+  return FALSE;
+}
+
+/**
  * Checks for usernames blocked by user administration.
  *
  * @param $name
@@ -526,6 +619,9 @@ function user_permission() {
       'title' => t('Administer users'),
       'restrict access' => TRUE,
     ),
+    'access users' => array(
+      'title' => t('Access users'),
+    ),
     'access user profiles' => array(
       'title' => t('View user profiles'),
     ),
@@ -950,11 +1046,12 @@ function template_preprocess_user_picture(&$variables) {
  */
 function template_preprocess_username(&$variables) {
   $account = $variables['account'];
+  $access_user = user_access_user('view', $account);
 
   $variables['extra'] = '';
   if (empty($account->uid)) {
    $variables['uid'] = 0;
-   if (theme_get_setting('toggle_comment_user_verification')) {
+   if ($access_user && theme_get_setting('toggle_comment_user_verification')) {
      $variables['extra'] = ' (' . t('not verified') . ')';
    }
   }
@@ -967,12 +1064,12 @@ function template_preprocess_username(&$variables) {
   // unsanitized version, in case other preprocess functions want to implement
   // their own shortening logic or add markup. If they do so, they must ensure
   // that $variables['name'] is safe for printing.
-  $name = $variables['name_raw'] = user_format_name($account);
+  $name = $variables['name_raw'] = $access_user ? user_format_name($account) :  t('an invisible user');
   if (drupal_strlen($name) > 20) {
     $name = drupal_substr($name, 0, 15) . '...';
   }
   $variables['name'] = check_plain($name);
-  $variables['profile_access'] = user_access('access user profiles');
+  $variables['profile_access'] = user_access_user('view_profile', $account);
 
   // Populate link path and attributes if appropriate.
   if ($variables['uid'] && $variables['profile_access']) {
@@ -1271,8 +1368,8 @@ function user_menu() {
     'title arguments' => array(1),
     'page callback' => 'user_view_page',
     'page arguments' => array(1),
-    'access callback' => 'user_view_access',
-    'access arguments' => array(1),
+    'access callback' => 'user_access_user',
+    'access arguments' => array('view_profile', 1),
   );
 
   $items['user/%user/view'] = array(
@@ -1285,8 +1382,8 @@ function user_menu() {
     'title' => 'Cancel account',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('user_cancel_confirm_form', 1),
-    'access callback' => 'user_cancel_access',
-    'access arguments' => array(1),
+    'access callback' => 'user_access_user',
+    'access arguments' => array('cancel', 1),
     'file' => 'user.pages.inc',
   );
 
@@ -1294,8 +1391,8 @@ function user_menu() {
     'title' => 'Confirm account cancellation',
     'page callback' => 'user_cancel_confirm',
     'page arguments' => array(1, 4, 5),
-    'access callback' => 'user_cancel_access',
-    'access arguments' => array(1),
+    'access callback' => 'user_access_user',
+    'access arguments' => array('cancel', 1),
     'file' => 'user.pages.inc',
   );
 
@@ -1303,8 +1400,8 @@ function user_menu() {
     'title' => 'Edit',
     'page callback' => 'entity_get_form',
     'page arguments' => array(1, 'profile'),
-    'access callback' => 'user_edit_access',
-    'access arguments' => array(1),
+    'access callback' => 'user_access_user',
+    'access arguments' => array('update', 1),
     'type' => MENU_LOCAL_TASK,
     'file' => 'user.pages.inc',
   );
diff --git a/core/profiles/minimal/minimal.install b/core/profiles/minimal/minimal.install
index 4e42e6d..9a436c2 100644
--- a/core/profiles/minimal/minimal.install
+++ b/core/profiles/minimal/minimal.install
@@ -60,6 +60,6 @@ function minimal_install() {
   config('user.settings')->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();
 
   // Enable default permissions for system roles.
-  user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access content'));
-  user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access content'));
+  user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access content', 'access users'));
+  user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access users'));
 }
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index b4dc761..f15acef 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -375,8 +375,8 @@ function standard_install() {
 
   // Enable default permissions for system roles.
   $filtered_html_permission = filter_permission_name($filtered_html_format);
-  user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access content', 'access comments', $filtered_html_permission));
-  user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'skip comment approval', $filtered_html_permission));
+  user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access content', 'access comments', $filtered_html_permission, 'access users'));
+  user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'skip comment approval', $filtered_html_permission, 'access users'));
 
   // Create a default role for site administrators, with all available permissions assigned.
   $admin_role = new stdClass();
