diff --git a/lib/resource/Directory.js b/lib/resource/Directory.js
index 3e456f6e..9afeb1b4 100644
--- a/lib/resource/Directory.js
+++ b/lib/resource/Directory.js
@@ -76,6 +76,22 @@ Directory.prototype.getAccountCreationPolicy = function getAccountCreationPolicy
return this.dataStore.getResource(this.accountCreationPolicy.href, args.options, require('./InstanceResource'), args.callback);
};
+/**
+ * Get the {@link Schema} resource of this Directory resource. The schema allows
+ * you to control which attributes are required when accounts are created in this
+ * directory.
+ *
+ * @param {ExpansionOptions} [expansionOptions]
+ * For retrieving linked resources of the {@link Schema} during this request.
+ *
+ * @param {Function} callback
+ * Callback function, will be called with (err, {@link Schema}).
+ */
+Directory.prototype.getAccountSchema = function getAccountSchema(/* [options,] callback */) {
+ var args = utils.resolveArgs(arguments, ['options', 'callback'], true);
+ return this.dataStore.getResource(this.accountSchema.href, args.options, require('./Schema'), args.callback);
+};
+
/**
* Get the {@link PasswordPolicy} resource of this Directory resource.
*
diff --git a/lib/resource/Field.js b/lib/resource/Field.js
new file mode 100644
index 00000000..d29cc3d2
--- /dev/null
+++ b/lib/resource/Field.js
@@ -0,0 +1,33 @@
+'use strict';
+
+var Resource = require('./Resource');
+var utils = require('../utils');
+
+/**
+ * @class Field
+ *
+ * @description
+ * Encapsulates an account field, as part of a {@link Schema}.
+ *
+ * This class should not be manually constructed. It should be obtained from one of these methods:
+ *
+ * - {@link Schema#getFields Schema.getFields()}.
+ */
+function Field(){
+ Field.super_.apply(this, arguments);
+}
+
+utils.inherits(Field, Resource);
+
+/**
+ * Save changes to this resource.
+ *
+ * @param {Function} callback
+ * The function to call when the save operation is complete. Will be called
+ * with the parameters (err, updatedResource).
+ */
+Field.prototype.save = function save(callback) {
+ this.dataStore.saveResource(this, callback);
+};
+
+module.exports = Field;
\ No newline at end of file
diff --git a/lib/resource/Schema.js b/lib/resource/Schema.js
new file mode 100644
index 00000000..021865c5
--- /dev/null
+++ b/lib/resource/Schema.js
@@ -0,0 +1,53 @@
+'use strict';
+
+var Resource = require('./Resource');
+var utils = require('../utils');
+
+/**
+ * @class Schema
+ *
+ * @description
+ * Encapsulates the Schema resource of a {@link Directory}. This schema allows you
+ * to control which Account attributes (referred to as fields) are required when
+ * creating new accounts in the directory. For full documentation of this resource, please see
+ * [How to Manage an Account’s Required Attributes](https://docs.stormpath.com/rest/product-guide/latest/accnt_mgmt.html#how-to-manage-an-account-s-required-attributes).
+ *
+ * This class should not be manually constructed. It should be obtained from one of these methods:
+ * - {@link Directory#getAccountSchema Directory.getAccountSchema()}.
+ *
+ * @example
Disabling a field requirement.
+ * var _ = require('lodash');
+ *
+ * schema.getFields(function (err, fieldsCollection) {
+ * var givenNameField = _.find(fieldsCollection.items, {
+ * name: 'givenName'
+ * });
+ *
+ * givenNameField.required = false;
+ *
+ * givenNameField.save();
+ * });
+ */
+function Schema() {
+ Schema.super_.apply(this, arguments);
+}
+
+utils.inherits(Schema, Resource);
+
+/**
+ * Get the collection of {@link Field Fields} for this schema.
+ *
+ * @param {CollectionQueryOptions} [options]
+ * Options for querying, paginating, and expanding the collection.
+ *
+ * @param {Function} callback
+ * The function to call when the operation is complete. Will be called
+ * with the parameters (err, {@link CollectionResource}). The collection will
+ * be a list of {@link Field} objects.
+ */
+Schema.prototype.getFields = function () {
+ var args = utils.resolveArgs(arguments, ['options', 'callback'], true);
+ return this.dataStore.getResource(this.fields.href, args.options, require('./Field'), args.callback);
+};
+
+module.exports = Schema;
\ No newline at end of file
diff --git a/test/sp.resource.directory_test.js b/test/sp.resource.directory_test.js
index 72a507f6..a9151476 100644
--- a/test/sp.resource.directory_test.js
+++ b/test/sp.resource.directory_test.js
@@ -9,6 +9,7 @@ var Account = require('../lib/resource/Account');
var Group = require('../lib/resource/Group');
var Organization = require('../lib/resource/Organization');
var OrganizationAccountStoreMapping = require('../lib/resource/OrganizationAccountStoreMapping');
+var Schema = require('../lib/resource/Schema');
var Tenant = require('../lib/resource/Tenant');
var Provider = require('../lib/resource/Provider');
var Directory = require('../lib/resource/Directory');
@@ -380,6 +381,49 @@ describe('Resources: ', function () {
});
});
+ describe('get account schema', function () {
+ describe('if accountSchema href is set', function () {
+ var opt;
+ var getResourceStub;
+ var sandbox;
+ var app;
+ var directory;
+ var cbSpy;
+
+ before(function () {
+ opt = {};
+ sandbox = sinon.sandbox.create();
+ app = { accountSchema: { href: 'boom!' } };
+ directory = new Directory(app, dataStore);
+ cbSpy = sandbox.spy();
+
+ getResourceStub = sandbox.stub(dataStore, 'getResource', function (href, options, ctor, cb) {
+ cb();
+ });
+
+ directory.getAccountSchema(cbSpy);
+ directory.getAccountSchema(opt, cbSpy);
+ });
+
+ after(function () {
+ sandbox.restore();
+ });
+
+ it('should get account schema', function () {
+ cbSpy.should.have.been.calledTwice;
+ getResourceStub.should.have.been.calledTwice;
+
+ getResourceStub.should.have.been.calledWith(
+ app.accountSchema.href, null, Schema, cbSpy
+ );
+
+ getResourceStub.should.have.been.calledWith(
+ app.accountSchema.href, opt, Schema, cbSpy
+ );
+ });
+ });
+ });
+
describe('get organization mappings', function () {
describe('if organizationMappings href are set', function () {
var opt;
diff --git a/test/sp.resource.field_test.js b/test/sp.resource.field_test.js
new file mode 100644
index 00000000..b424b15c
--- /dev/null
+++ b/test/sp.resource.field_test.js
@@ -0,0 +1,47 @@
+'use strict';
+
+var common = require('./common');
+var sinon = common.sinon;
+
+var DataStore = require('../lib/ds/DataStore');
+var Field = require('../lib/resource/Field');
+
+describe('Field Resource', function () {
+ describe('save()', function () {
+ var sandbox;
+ var field;
+ var dataStore;
+ var requestExecutorStub;
+
+ var mockField = {
+ href: 'https://api.stormpath.com/v1/fields/7dDfMOkrekkLhbWBLcGWuN',
+ createdAt: '2016-08-02T20:16:21.931Z',
+ modifiedAt: '2016-08-02T20:16:21.931Z',
+ name: 'givenName',
+ required: true,
+ schema: {
+ href: 'https://api.stormpath.com/v1/schemas/7dDfMLQmkARN4mQK9MPGIJ'
+ }
+ };
+
+ before(function () {
+ dataStore = new DataStore({client: {apiKey: {id: 1, secret: 2}}});
+ sandbox = sinon.sandbox.create();
+ field = new Field(mockField, dataStore);
+ requestExecutorStub = sandbox.stub(dataStore.requestExecutor, 'execute');
+ });
+
+ after(function () {
+ sandbox.restore();
+ });
+
+ it('should post the resource to the REST API', function () {
+ field.save();
+ requestExecutorStub.should.have.been.calledWith({
+ body: mockField,
+ uri: mockField.href,
+ method: 'POST'
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/test/sp.resource.schema_test.js b/test/sp.resource.schema_test.js
new file mode 100644
index 00000000..52933cf8
--- /dev/null
+++ b/test/sp.resource.schema_test.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var common = require('./common');
+var assert = common.assert;
+var sinon = common.sinon;
+
+var DataStore = require('../lib/ds/DataStore');
+var Field = require('../lib/resource/Field');
+var Schema = require('../lib/resource/Schema');
+
+describe('Schema Resource', function(){
+
+ describe('getFields()', function(){
+
+ var sandbox;
+ var schema;
+ var dataStore;
+
+ var mockSchema = {
+ href: 'https://api.stormpath.com/v1/schemas/7cvoYkLuGzpnAuVlKQdiDf/',
+ fields: {
+ href: 'https://api.stormpath.com/v1/schemas/7cvoYkLuGzpnAuVlKQdiDf/fields'
+ }
+ };
+
+ var mockFieldsResponse = {
+ href: 'https://api.stormpath.com/v1/schemas/7dDfMLQmkARN4mQK9MPGIJ/fields',
+ offset: 0,
+ limit: 25,
+ size: 2,
+ items: [
+ {
+ href: 'https://api.stormpath.com/v1/fields/7dDfMOkrekkLhbWBLcGWuN',
+ createdAt: '2016-08-02T20:16:21.931Z',
+ modifiedAt: '2016-08-02T20:16:21.931Z',
+ name: 'givenName',
+ required: true,
+ schema: {
+ href: 'https://api.stormpath.com/v1/schemas/7dDfMLQmkARN4mQK9MPGIJ'
+ }
+ },
+ ]
+ };
+
+
+
+ before(function(){
+ dataStore = new DataStore({client: {apiKey: {id: 1, secret: 2}}});
+ sandbox = sinon.sandbox.create();
+ schema = new Schema(mockSchema, dataStore);
+
+ sandbox.stub(dataStore.requestExecutor, 'execute', function (req, cb) {
+ cb(null, mockFieldsResponse);
+ });
+ });
+
+ after(function(){
+ sandbox.restore();
+ });
+
+ it('should return Field instances', function(done){
+ schema.getFields(function(err, result){
+ assert.equal(result.items, mockFieldsResponse.items);
+ assert.instanceOf(mockFieldsResponse.items[0], Field);
+ done();
+ });
+ });
+ });
+});
\ No newline at end of file