diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js
index 99ec4910d1..2de059f38a 100644
--- a/spec/CloudCode.spec.js
+++ b/spec/CloudCode.spec.js
@@ -1163,6 +1163,56 @@ describe('Cloud Code', () => {
     );
   });
 
+  it('test save triggers get user roles', async () => {
+    let beforeSaveFlag = false;
+    let afterSaveFlag = false;
+    Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => {
+      expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2']));
+      beforeSaveFlag = true;
+    });
+
+    Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => {
+      expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2']));
+      afterSaveFlag = true;
+    });
+
+    const user = new Parse.User();
+    user.set('password', 'asdf');
+    user.set('email', 'asdf@example.com');
+    user.set('username', 'zxcv');
+    await user.signUp();
+    const role1 = new Parse.Role('TestRole1', new Parse.ACL({ '*': { read: true, write: true } }));
+    const role2 = new Parse.Role('TestRole2', new Parse.ACL({ '*': { read: true, write: true } }));
+    await role1.save();
+    role2.getRoles().add(role1);
+    role1.getUsers().add(user);
+    await Parse.Object.saveAll([role1, role2]);
+
+    const obj = new Parse.Object('SaveTriggerUserRoles');
+    await obj.save();
+    expect(beforeSaveFlag).toBeTrue();
+    expect(afterSaveFlag).toBeTrue();
+  });
+
+  it('should not have user roles for anonymous calls', async () => {
+    let beforeSaveFlag = false;
+    let afterSaveFlag = false;
+    Parse.Cloud.beforeSave('SaveTriggerUserRoles', async req => {
+      expect(req.getUserRoles).toBeUndefined();
+      beforeSaveFlag = true;
+    });
+
+    Parse.Cloud.afterSave('SaveTriggerUserRoles', async req => {
+      expect(req.getUserRoles).toBeUndefined();
+      afterSaveFlag = true;
+    });
+
+    const obj = new Parse.Object('SaveTriggerUserRoles');
+    await obj.save();
+    expect(beforeSaveFlag).toBeTrue();
+    expect(afterSaveFlag).toBeTrue();
+  });
+
   it('beforeSave change propagates through the save response', done => {
     Parse.Cloud.beforeSave('ChangingObject', function (request) {
       request.object.set('foo', 'baz');
@@ -2014,6 +2064,40 @@ describe('cloud functions', () => {
 
     Parse.Cloud.run('myFunction', {}).then(() => done());
   });
+
+  it('should have user roles', async () => {
+    let flag = false;
+    Parse.Cloud.define('myFunction', async req => {
+      expect(new Set(await req.getUserRoles())).toEqual(new Set(['TestRole1', 'TestRole2']));
+      flag = true;
+    });
+
+    const user = new Parse.User();
+    user.set('password', 'asdf');
+    user.set('email', 'asdf@example.com');
+    user.set('username', 'zxcv');
+    await user.signUp();
+    const role1 = new Parse.Role('TestRole1', new Parse.ACL({ '*': { read: true, write: true } }));
+    const role2 = new Parse.Role('TestRole2', new Parse.ACL({ '*': { read: true, write: true } }));
+    await role1.save();
+    role2.getRoles().add(role1);
+    role1.getUsers().add(user);
+    await Parse.Object.saveAll([role1, role2]);
+
+    await Parse.Cloud.run('myFunction', { sessionToken: user.getSessionToken() });
+    expect(flag).toBeTrue();
+  });
+
+  it('should not have user roles for anonymous calls', async () => {
+    let flag = false;
+    Parse.Cloud.define('myFunction', async req => {
+      expect(req.getUserRoles).toBeUndefined();
+      flag = true;
+    });
+
+    await Parse.Cloud.run('myFunction');
+    expect(flag).toBeTrue();
+  });
 });
 
 describe('beforeSave hooks', () => {
diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js
index 77c0dff7cc..301b3cb123 100644
--- a/src/Routers/FunctionsRouter.js
+++ b/src/Routers/FunctionsRouter.js
@@ -8,6 +8,7 @@ import { promiseEnforceMasterKeyAccess, promiseEnsureIdempotency } from '../midd
 import { jobStatusHandler } from '../StatusHandler';
 import _ from 'lodash';
 import { logger } from '../logger';
+import Utils from '../Utils';
 
 function parseObject(obj, config) {
   if (Array.isArray(obj)) {
@@ -131,6 +132,12 @@ export class FunctionsRouter extends PromiseRouter {
       params: params,
       master: req.auth && req.auth.isMaster,
       user: req.auth && req.auth.user,
+      getUserRoles:
+        req.auth && req.auth.user
+          ? async () => {
+              return (await req.auth.getUserRoles()).map(Utils.stripACLRolePrefix);
+            }
+          : undefined,
       installationId: req.info.installationId,
       log: req.config.loggerController,
       headers: req.config.headers,
diff --git a/src/Utils.js b/src/Utils.js
index b77a3d85d7..1466cc1449 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -399,6 +399,18 @@ class Utils {
     }
     return obj;
   }
+
+  /**
+   * Strips the "role:" prefix from the role name as it appears in the ACL.
+   *
+   * @param {String} entry The role name prefixed with the string "role:".
+   * @returns {String} The role name, without the "role": prefix.
+   * @example
+   * stripACLRolePrefix("role:myrole") // Returns "myrole"
+   */
+  static stripACLRolePrefix(entry) {
+    return entry.substr(5 /* 'role:'.length */);
+  }
 }
 
 module.exports = Utils;
diff --git a/src/triggers.js b/src/triggers.js
index e34c5fd3a8..3e3db04a7c 100644
--- a/src/triggers.js
+++ b/src/triggers.js
@@ -1,6 +1,7 @@
 // triggers.js
 import Parse from 'parse/node';
 import { logger } from './logger';
+import Utils from './Utils';
 
 export const Types = {
   beforeLogin: 'beforeLogin',
@@ -292,6 +293,9 @@ export function getRequestObject(
   }
   if (auth.user) {
     request['user'] = auth.user;
+    request['getUserRoles'] = async () => {
+      return (await auth.getUserRoles()).map(Utils.stripACLRolePrefix);
+    };
   }
   if (auth.installationId) {
     request['installationId'] = auth.installationId;