From e14b7cd0e28f0ca6f1ffbdaeca0521d33c9b024c Mon Sep 17 00:00:00 2001
From: Jamie V <jamie.j.vigliotta@nasa.gov>
Date: Thu, 20 Jan 2022 13:56:17 -0800
Subject: [PATCH] [UserAPI] [UserIndicator Plugin] [ExampleUserProvider Plugin]
 New user api and related functionality (#4538)

* Implements User API and example user plugin

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
---
 example/exampleUser/ExampleUserProvider.js    | 110 ++++++++++++++
 example/exampleUser/exampleUserCreator.js     |  36 +++++
 example/exampleUser/plugin.js                 |  29 ++++
 example/exampleUser/pluginSpec.js             |  55 +++++++
 index.html                                    |   7 +-
 src/MCT.js                                    |  10 ++
 src/api/api.js                                |   9 +-
 src/api/forms/components/FormProperties.vue   |  26 +++-
 src/api/user/User.js                          |  39 +++++
 src/api/user/UserAPI.js                       | 137 ++++++++++++++++++
 src/api/user/UserAPISpec.js                   | 112 ++++++++++++++
 src/api/user/constants.js                     |  24 +++
 src/plugins/plugins.js                        |  17 ++-
 .../components/UserIndicator.vue              |  54 +++++++
 src/plugins/userIndicator/plugin.js           |  56 +++++++
 src/plugins/userIndicator/pluginSpec.js       | 100 +++++++++++++
 src/styles/_controls.scss                     |   1 +
 17 files changed, 808 insertions(+), 14 deletions(-)
 create mode 100644 example/exampleUser/ExampleUserProvider.js
 create mode 100644 example/exampleUser/exampleUserCreator.js
 create mode 100644 example/exampleUser/plugin.js
 create mode 100644 example/exampleUser/pluginSpec.js
 create mode 100644 src/api/user/User.js
 create mode 100644 src/api/user/UserAPI.js
 create mode 100644 src/api/user/UserAPISpec.js
 create mode 100644 src/api/user/constants.js
 create mode 100644 src/plugins/userIndicator/components/UserIndicator.vue
 create mode 100644 src/plugins/userIndicator/plugin.js
 create mode 100644 src/plugins/userIndicator/pluginSpec.js

diff --git a/example/exampleUser/ExampleUserProvider.js b/example/exampleUser/ExampleUserProvider.js
new file mode 100644
index 0000000000..dc49467b1e
--- /dev/null
+++ b/example/exampleUser/ExampleUserProvider.js
@@ -0,0 +1,110 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import EventEmitter from 'EventEmitter';
+import uuid from 'uuid';
+import createExampleUser from './exampleUserCreator';
+
+export default class ExampleUserProvider extends EventEmitter {
+    constructor(openmct) {
+        super();
+
+        this.openmct = openmct;
+        this.user = undefined;
+        this.loggedIn = false;
+        this.autoLoginUser = undefined;
+
+        this.ExampleUser = createExampleUser(this.openmct.user.User);
+    }
+
+    isLoggedIn() {
+        return this.loggedIn;
+    }
+
+    autoLogin(username) {
+        this.autoLoginUser = username;
+    }
+
+    getCurrentUser() {
+        if (this.loggedIn) {
+            return Promise.resolve(this.user);
+        }
+
+        return this._login().then(() => this.user);
+    }
+
+    hasRole(roleId) {
+        if (!this.loggedIn) {
+            Promise.resolve(undefined);
+        }
+
+        return Promise.resolve(this.user.getRoles().includes(roleId));
+    }
+
+    _login() {
+        const id = uuid();
+
+        // for testing purposes, this will skip the form, this wouldn't be used in
+        // a normal authentication process
+        if (this.autoLoginUser) {
+            this.user = new this.ExampleUser(id, this.autoLoginUser, ['example-role']);
+            this.loggedIn = true;
+
+            return Promise.resolve();
+        }
+
+        const formStructure = {
+            title: "Login",
+            sections: [
+                {
+                    rows: [
+                        {
+                            key: "username",
+                            control: "textfield",
+                            name: "Username",
+                            pattern: "\\S+",
+                            required: true,
+                            cssClass: "l-input-lg",
+                            value: ''
+                        }
+                    ]
+                }
+            ],
+            buttons: {
+                submit: {
+                    label: 'Login'
+                }
+            }
+        };
+
+        return this.openmct.forms.showForm(formStructure).then(
+            (info) => {
+                this.user = new this.ExampleUser(id, info.username, ['example-role']);
+                this.loggedIn = true;
+            },
+            () => { // user canceled, setting a default username
+                this.user = new this.ExampleUser(id, 'Pat', ['example-role']);
+                this.loggedIn = true;
+            }
+        );
+    }
+}
diff --git a/example/exampleUser/exampleUserCreator.js b/example/exampleUser/exampleUserCreator.js
new file mode 100644
index 0000000000..286cc611b5
--- /dev/null
+++ b/example/exampleUser/exampleUserCreator.js
@@ -0,0 +1,36 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+export default function createExampleUser(UserClass) {
+    return class ExampleUser extends UserClass {
+        constructor(id, name, roles) {
+            super(id, name);
+
+            this.roles = roles;
+            this.getRoles = this.getRoles.bind(this);
+        }
+
+        getRoles() {
+            return this.roles;
+        }
+    };
+}
diff --git a/example/exampleUser/plugin.js b/example/exampleUser/plugin.js
new file mode 100644
index 0000000000..ba9ee38040
--- /dev/null
+++ b/example/exampleUser/plugin.js
@@ -0,0 +1,29 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import ExampleUserProvider from './ExampleUserProvider';
+
+export default function ExampleUserPlugin() {
+    return function install(openmct) {
+        openmct.user.setProvider(new ExampleUserProvider(openmct));
+    };
+}
diff --git a/example/exampleUser/pluginSpec.js b/example/exampleUser/pluginSpec.js
new file mode 100644
index 0000000000..f62d157359
--- /dev/null
+++ b/example/exampleUser/pluginSpec.js
@@ -0,0 +1,55 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import {
+    createOpenMct,
+    resetApplicationState
+} from '../../src/utils/testing';
+import ExampleUserProvider from './ExampleUserProvider';
+
+describe("The Example User Plugin", () => {
+    let openmct;
+
+    beforeEach(() => {
+        openmct = createOpenMct();
+    });
+
+    afterEach(() => {
+        return resetApplicationState(openmct);
+    });
+
+    it('is not installed by default', () => {
+        expect(openmct.user.hasProvider()).toBeFalse();
+    });
+
+    it('can be installed', () => {
+        openmct.user.on('providerAdded', (provider) => {
+            expect(provider).toBeInstanceOf(ExampleUserProvider);
+        });
+        openmct.install(openmct.plugins.example.ExampleUser());
+    });
+
+    // The rest of the functionality of the ExampleUser Plugin is
+    // tested in both the UserAPISpec.js and in the UserIndicatorPlugin spec.
+    // If that changes, those tests can be moved here.
+
+});
diff --git a/index.html b/index.html
index fdad41c667..7cbb564cde 100644
--- a/index.html
+++ b/index.html
@@ -77,12 +77,13 @@
 
 
         openmct.install(openmct.plugins.LocalStorage());
+      
+        openmct.install(openmct.plugins.example.Generator());
+        openmct.install(openmct.plugins.example.EventGeneratorPlugin());
+        openmct.install(openmct.plugins.example.ExampleImagery());
 
         openmct.install(openmct.plugins.Espresso());
         openmct.install(openmct.plugins.MyItems());
-        openmct.install(openmct.plugins.Generator());
-        openmct.install(openmct.plugins.EventGeneratorPlugin());
-        openmct.install(openmct.plugins.ExampleImagery());
         openmct.install(openmct.plugins.PlanLayout());
         openmct.install(openmct.plugins.Timeline());
         openmct.install(openmct.plugins.Hyperlink());
diff --git a/src/MCT.js b/src/MCT.js
index fa1848d7ad..7168bfb2cb 100644
--- a/src/MCT.js
+++ b/src/MCT.js
@@ -212,6 +212,15 @@ define([
          */
         this.indicators = new api.IndicatorAPI(this);
 
+        /**
+         * MCT's user awareness management, to enable user and
+         * role specific functionality.
+         * @type {module:openmct.UserAPI}
+         * @memberof module:openmct.MCT#
+         * @name user
+         */
+        this.user = new api.UserAPI(this);
+
         this.notifications = new api.NotificationAPI();
 
         this.editor = new api.EditorAPI.default(this);
@@ -262,6 +271,7 @@ define([
         this.install(this.plugins.ObjectInterceptors());
         this.install(this.plugins.NonEditableFolder());
         this.install(this.plugins.DeviceClassifier());
+        this.install(this.plugins.UserIndicator());
     }
 
     MCT.prototype = Object.create(EventEmitter.prototype);
diff --git a/src/api/api.js b/src/api/api.js
index 96a2691e37..1a0174d574 100644
--- a/src/api/api.js
+++ b/src/api/api.js
@@ -33,7 +33,8 @@ define([
     './status/StatusAPI',
     './telemetry/TelemetryAPI',
     './time/TimeAPI',
-    './types/TypeRegistry'
+    './types/TypeRegistry',
+    './user/UserAPI'
 ], function (
     ActionsAPI,
     CompositionAPI,
@@ -47,7 +48,8 @@ define([
     StatusAPI,
     TelemetryAPI,
     TimeAPI,
-    TypeRegistry
+    TypeRegistry,
+    UserAPI
 ) {
     return {
         ActionsAPI: ActionsAPI.default,
@@ -62,6 +64,7 @@ define([
         StatusAPI: StatusAPI.default,
         TelemetryAPI: TelemetryAPI,
         TimeAPI: TimeAPI.default,
-        TypeRegistry: TypeRegistry
+        TypeRegistry: TypeRegistry,
+        UserAPI: UserAPI.default
     };
 });
diff --git a/src/api/forms/components/FormProperties.vue b/src/api/forms/components/FormProperties.vue
index 9ae202194f..d3caebea6c 100644
--- a/src/api/forms/components/FormProperties.vue
+++ b/src/api/forms/components/FormProperties.vue
@@ -60,13 +60,13 @@
                 class="c-button c-button--major"
                 @click="onSave"
         >
-            OK
+            {{ submitLabel }}
         </button>
         <button tabindex="0"
                 class="c-button"
                 @click="onDismiss"
         >
-            Cancel
+            {{ cancelLabel }}
         </button>
     </div>
 </div>
@@ -105,6 +105,28 @@ export default {
                 .some(([key, value]) => {
                     return value;
                 });
+        },
+        submitLabel() {
+            if (
+                this.model.buttons
+                && this.model.buttons.submit
+                && this.model.buttons.submit.label
+            ) {
+                return this.model.buttons.submit.label;
+            }
+
+            return 'OK';
+        },
+        cancelLabel() {
+            if (
+                this.model.buttons
+                && this.model.buttons.cancel
+                && this.model.buttons.cancel.label
+            ) {
+                return this.model.buttons.submit.label;
+            }
+
+            return 'Cancel';
         }
     },
     mounted() {
diff --git a/src/api/user/User.js b/src/api/user/User.js
new file mode 100644
index 0000000000..dfb0caa0cf
--- /dev/null
+++ b/src/api/user/User.js
@@ -0,0 +1,39 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+export default class User {
+    constructor(id, name) {
+        this.id = id;
+        this.name = name;
+
+        this.getId = this.getId.bind(this);
+        this.getName = this.getName.bind(this);
+    }
+
+    getId() {
+        return this.id;
+    }
+
+    getName() {
+        return this.name;
+    }
+}
diff --git a/src/api/user/UserAPI.js b/src/api/user/UserAPI.js
new file mode 100644
index 0000000000..82ff21f8d7
--- /dev/null
+++ b/src/api/user/UserAPI.js
@@ -0,0 +1,137 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import EventEmitter from 'EventEmitter';
+import {
+    MULTIPLE_PROVIDER_ERROR,
+    NO_PROVIDER_ERROR
+} from './constants';
+import User from './User';
+
+class UserAPI extends EventEmitter {
+    constructor(openmct) {
+        super();
+
+        this._openmct = openmct;
+        this._provider = undefined;
+
+        this.User = User;
+    }
+
+    /**
+     * Set the user provider for the user API. This allows you
+     *  to specifiy ONE user provider to be used with Open MCT.
+     * @method setProvider
+     * @memberof module:openmct.UserAPI#
+     * @param {module:openmct.UserAPI~UserProvider} provider the new
+     *        user provider
+     */
+    setProvider(provider) {
+        if (this.hasProvider()) {
+            this._error(MULTIPLE_PROVIDER_ERROR);
+        }
+
+        this._provider = provider;
+
+        this.emit('providerAdded', this._provider);
+    }
+
+    /**
+     * Return true if the user provider has been set.
+     *
+     * @memberof module:openmct.UserAPI#
+     * @returns {boolean} true if the user provider exists
+     */
+    hasProvider() {
+        return this._provider !== undefined;
+    }
+
+    /**
+     * If a user provider is set, it will return a copy of a user object from
+     * the provider. If the user is not logged in, it will return undefined;
+     *
+     * @memberof module:openmct.UserAPI#
+     * @returns {Function|Promise} user provider 'getCurrentUser' method
+     * @throws Will throw an error if no user provider is set
+     */
+    getCurrentUser() {
+        this._noProviderCheck();
+
+        return this._provider.getCurrentUser();
+    }
+
+    /**
+     * If a user provider is set, it will return the user provider's
+     * 'isLoggedIn' method
+     *
+     * @memberof module:openmct.UserAPI#
+     * @returns {Function|Boolean} user provider 'isLoggedIn' method
+     * @throws Will throw an error if no user provider is set
+     */
+    isLoggedIn() {
+        if (!this.hasProvider()) {
+            return false;
+        }
+
+        return this._provider.isLoggedIn();
+    }
+
+    /**
+     * If a user provider is set, it will return a call to it's
+     * 'hasRole' method
+     *
+     * @memberof module:openmct.UserAPI#
+     * @returns {Function|Boolean} user provider 'isLoggedIn' method
+     * @param {string} roleId id of role to check for
+     * @throws Will throw an error if no user provider is set
+     */
+    hasRole(roleId) {
+        this._noProviderCheck();
+
+        return this._provider.hasRole(roleId);
+    }
+
+    /**
+     * Checks if a provider is set and if not, will throw error
+     *
+     * @private
+     * @throws Will throw an error if no user provider is set
+     */
+    _noProviderCheck() {
+        if (!this.hasProvider()) {
+            this._error(NO_PROVIDER_ERROR);
+        }
+    }
+
+    /**
+     * Utility function for throwing errors
+     *
+     * @private
+     * @param {string} error description of error
+     * @throws Will throw error passed in
+     */
+    _error(error) {
+        throw new Error(error);
+    }
+}
+
+export default UserAPI;
diff --git a/src/api/user/UserAPISpec.js b/src/api/user/UserAPISpec.js
new file mode 100644
index 0000000000..c70066e3f4
--- /dev/null
+++ b/src/api/user/UserAPISpec.js
@@ -0,0 +1,112 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import {
+    createOpenMct,
+    resetApplicationState
+} from '../../utils/testing';
+import {
+    MULTIPLE_PROVIDER_ERROR
+} from './constants';
+import ExampleUserProvider from '../../../example/exampleUser/ExampleUserProvider';
+
+const USERNAME = 'Test User';
+const EXAMPLE_ROLE = 'example-role';
+
+describe("The User API", () => {
+    let openmct;
+
+    beforeEach(() => {
+        openmct = createOpenMct();
+    });
+
+    afterEach(() => {
+        return resetApplicationState(openmct);
+    });
+
+    describe('with regard to user providers', () => {
+
+        it('allows you to specify a user provider', () => {
+            openmct.user.on('providerAdded', (provider) => {
+                expect(provider).toBeInstanceOf(ExampleUserProvider);
+            });
+            openmct.user.setProvider(new ExampleUserProvider(openmct));
+        });
+
+        it('prevents more than one user provider from being set', () => {
+            openmct.user.setProvider(new ExampleUserProvider(openmct));
+
+            expect(() => {
+                openmct.user.setProvider({});
+            }).toThrow(new Error(MULTIPLE_PROVIDER_ERROR));
+        });
+
+        it('provides a check for an existing user provider', () => {
+            expect(openmct.user.hasProvider()).toBeFalse();
+
+            openmct.user.setProvider(new ExampleUserProvider(openmct));
+
+            expect(openmct.user.hasProvider()).toBeTrue();
+        });
+    });
+
+    describe('provides the ability', () => {
+        let provider;
+
+        beforeEach(() => {
+            provider = new ExampleUserProvider(openmct);
+            provider.autoLogin(USERNAME);
+        });
+
+        it('to check if a user (not specific) is loged in', (done) => {
+            expect(openmct.user.isLoggedIn()).toBeFalse();
+
+            openmct.user.on('providerAdded', () => {
+                expect(openmct.user.isLoggedIn()).toBeTrue();
+                done();
+            });
+
+            // this will trigger the user indicator plugin,
+            // which will in turn login the user
+            openmct.user.setProvider(provider);
+        });
+
+        it('to get the current user', (done) => {
+            openmct.user.setProvider(provider);
+            openmct.user.getCurrentUser().then((apiUser) => {
+                expect(apiUser.name).toEqual(USERNAME);
+            }).finally(done);
+        });
+
+        it('to check if a user has a specific role (by id)', (done) => {
+            openmct.user.setProvider(provider);
+            let junkIdCheckPromise = openmct.user.hasRole('junk-id').then((hasRole) => {
+                expect(hasRole).toBeFalse();
+            });
+            let realIdCheckPromise = openmct.user.hasRole(EXAMPLE_ROLE).then((hasRole) => {
+                expect(hasRole).toBeTrue();
+            });
+
+            Promise.all([junkIdCheckPromise, realIdCheckPromise]).finally(done);
+        });
+    });
+});
diff --git a/src/api/user/constants.js b/src/api/user/constants.js
new file mode 100644
index 0000000000..0f839385cb
--- /dev/null
+++ b/src/api/user/constants.js
@@ -0,0 +1,24 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+export const MULTIPLE_PROVIDER_ERROR = 'Only one user provider may be set at a time.';
+export const NO_PROVIDER_ERROR = 'No user provider has been set.';
diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js
index a9cca07336..4755895aa6 100644
--- a/src/plugins/plugins.js
+++ b/src/plugins/plugins.js
@@ -75,6 +75,8 @@ define([
     './clock/plugin',
     './DeviceClassifier/plugin',
     './timer/plugin',
+    './userIndicator/plugin',
+    '../../example/exampleUser/plugin',
     './localStorage/plugin',
     './legacySupport/plugin.js',
     '../adapter/indicators/legacy-indicators-plugin'
@@ -133,6 +135,8 @@ define([
     Clock,
     DeviceClassifier,
     Timer,
+    UserIndicator,
+    ExampleUser,
     LocalStorage,
     LegacySupportPlugin,
     LegacyIndicatorsPlugin
@@ -149,6 +153,12 @@ define([
         };
     });
 
+    plugins.example = {};
+    plugins.example.ExampleUser = ExampleUser.default;
+    plugins.example.ExampleImagery = ExampleImagery.default;
+    plugins.example.EventGeneratorPlugin = EventGeneratorPlugin.default;
+    plugins.example.Generator = () => GeneratorPlugin;
+
     plugins.UTCTimeSystem = UTCTimeSystem.default;
     plugins.LocalTimeSystem = LocalTimeSystem;
     plugins.RemoteClock = RemoteClock.default;
@@ -194,12 +204,6 @@ define([
         };
     };
 
-    plugins.Generator = function () {
-        return GeneratorPlugin;
-    };
-
-    plugins.EventGeneratorPlugin = EventGeneratorPlugin.default;
-    plugins.ExampleImagery = ExampleImagery.default;
     plugins.ImageryPlugin = ImageryPlugin;
     plugins.Plot = PlotPlugin.default;
     plugins.Chart = ChartPlugin.default;
@@ -243,6 +247,7 @@ define([
     plugins.Clock = Clock.default;
     plugins.Timer = Timer.default;
     plugins.DeviceClassifier = DeviceClassifier.default;
+    plugins.UserIndicator = UserIndicator.default;
     plugins.LocalStorage = LocalStorage.default;
     plugins.LegacySupport = LegacySupportPlugin.default;
     plugins.LegacyIndicators = LegacyIndicatorsPlugin;
diff --git a/src/plugins/userIndicator/components/UserIndicator.vue b/src/plugins/userIndicator/components/UserIndicator.vue
new file mode 100644
index 0000000000..c063a88b14
--- /dev/null
+++ b/src/plugins/userIndicator/components/UserIndicator.vue
@@ -0,0 +1,54 @@
+<!--
+ Open MCT, Copyright (c) 2014-2021, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+
+<template>
+<div class="c-indicator icon-person c-indicator--clickable">
+    <span class="label c-indicator__label">
+        {{ userName }}
+    </span>
+</div>
+</template>
+
+<script>
+
+export default {
+    inject: ['openmct'],
+    data() {
+        return {
+            userName: undefined,
+            loggedIn: false
+        };
+    },
+
+    mounted() {
+        this.getUserInfo();
+    },
+    methods: {
+        getUserInfo() {
+            this.openmct.user.getCurrentUser().then((user) => {
+                this.userName = user.getName();
+                this.loggedIn = this.openmct.user.isLoggedIn();
+            });
+        }
+    }
+};
+</script>
diff --git a/src/plugins/userIndicator/plugin.js b/src/plugins/userIndicator/plugin.js
new file mode 100644
index 0000000000..9c46d252cd
--- /dev/null
+++ b/src/plugins/userIndicator/plugin.js
@@ -0,0 +1,56 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import UserIndicator from './components/UserIndicator.vue';
+import Vue from 'vue';
+
+export default function UserIndicatorPlugin() {
+
+    function addIndicator(openmct) {
+        const userIndicator = new Vue ({
+            components: {
+                UserIndicator
+            },
+            provide: {
+                openmct: openmct
+            },
+            template: '<UserIndicator />'
+        });
+
+        openmct.indicators.add({
+            key: 'user-indicator',
+            element: userIndicator.$mount().$el,
+            priority: openmct.priority.HIGH
+        });
+    }
+
+    return function install(openmct) {
+        if (openmct.user.hasProvider()) {
+            addIndicator(openmct);
+        } else {
+            // back up if user provider added after indicator installed
+            openmct.user.on('providerAdded', () => {
+                addIndicator(openmct);
+            });
+        }
+    };
+}
diff --git a/src/plugins/userIndicator/pluginSpec.js b/src/plugins/userIndicator/pluginSpec.js
new file mode 100644
index 0000000000..045e0a171f
--- /dev/null
+++ b/src/plugins/userIndicator/pluginSpec.js
@@ -0,0 +1,100 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * 'License'); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import {
+    createOpenMct,
+    resetApplicationState
+} from 'utils/testing';
+import Vue from 'vue';
+import ExampleUserProvider from '../../../example/exampleUser/ExampleUserProvider';
+
+const USERNAME = 'Coach McGuirk';
+
+describe('The User Indicator plugin', () => {
+    let openmct;
+    let element;
+    let child;
+    let appHolder;
+    let userIndicator;
+    let provider;
+
+    beforeEach((done) => {
+        appHolder = document.createElement('div');
+        appHolder.style.width = '640px';
+        appHolder.style.height = '480px';
+        document.body.appendChild(appHolder);
+
+        element = document.createElement('div');
+        child = document.createElement('div');
+        element.appendChild(child);
+
+        openmct = createOpenMct();
+        openmct.on('start', done);
+        openmct.start(appHolder);
+    });
+
+    afterEach(() => {
+        return resetApplicationState(openmct);
+    });
+
+    it('will not show, if there is no user provider', () => {
+        userIndicator = openmct.indicators.indicatorObjects
+            .find(indicator => indicator.key === 'user-indicator');
+
+        expect(userIndicator).toBe(undefined);
+    });
+
+    describe('with a user provider installed', () => {
+
+        beforeEach(() => {
+            provider = new ExampleUserProvider(openmct);
+            provider.autoLogin(USERNAME);
+
+            openmct.user.setProvider(provider);
+
+            return Vue.nextTick();
+        });
+
+        it('exists', () => {
+            userIndicator = openmct.indicators.indicatorObjects
+                .find(indicator => indicator.key === 'user-indicator').element;
+
+            const hasClockIndicator = userIndicator !== null && userIndicator !== undefined;
+            expect(hasClockIndicator).toBe(true);
+        });
+
+        it('contains the logged in user name', (done) => {
+            openmct.user.getCurrentUser().then(async (user) => {
+                await Vue.nextTick();
+
+                userIndicator = openmct.indicators.indicatorObjects
+                    .find(indicator => indicator.key === 'user-indicator').element;
+
+                const userName = userIndicator.textContent.trim();
+
+                expect(user.name).toEqual(USERNAME);
+                expect(userName).toContain(USERNAME);
+            }).finally(done);
+        });
+
+    });
+});
diff --git a/src/styles/_controls.scss b/src/styles/_controls.scss
index 30a8de3619..0e36cb6c7d 100644
--- a/src/styles/_controls.scss
+++ b/src/styles/_controls.scss
@@ -273,6 +273,7 @@ input, textarea {
 input[type=text],
 input[type=search],
 input[type=number],
+input[type=password],
 input[type=date],
 textarea {
     @include reactive-input();