diff --git a/azure_ad_client.js b/azure_ad_client.js index b84c89a..a7e10f4 100755 --- a/azure_ad_client.js +++ b/azure_ad_client.js @@ -12,7 +12,14 @@ AzureAd.requestCredential = function (options, credentialRequestCompleteCallback options = {}; } - var config = AzureAd.getConfiguration(); + var config = AzureAd.getConfiguration(true); + if (!config) { + credentialRequestCompleteCallback && credentialRequestCompleteCallback( + new ServiceConfiguration.ConfigError()); + return; + } + + var loginStyle = OAuth._loginStyle('azureAd', config, options); var credentialToken = Random.secret(); var baseUrl = "https://login.windows.net/" + config.tennantId + "/oauth2/authorize?"; diff --git a/azure_ad_server.js b/azure_ad_server.js index e89c5d4..f41f24f 100755 --- a/azure_ad_server.js +++ b/azure_ad_server.js @@ -1,3 +1,5 @@ +AzureAd.whitelistedFields = ['objectId', 'userPrincipleName', 'mail', 'displayName', 'surname', 'givenName']; + OAuth.registerService('azureAd', 2, null, function(query) { var tokens = getTokensFromCode(AzureAd.resources.graph.resourceUri, query.code); @@ -7,7 +9,7 @@ OAuth.registerService('azureAd', 2, null, function(query) { expiresAt: (+new Date) + (1000 * tokens.expiresIn) }; - var fields = _.pick(graphUser, AzureAd.resources.graph.whitelistedFields); + var fields = _.pick(graphUser, AzureAd.whitelistedFields); //must re-write the objectId field to id - meteor expects a field named "id" fields.id = fields.objectId; //we should add diff --git a/lib/azureAd.js b/lib/azureAd.js index 5fb2041..81a8427 100644 --- a/lib/azureAd.js +++ b/lib/azureAd.js @@ -1 +1,16 @@ -AzureAd = {} \ No newline at end of file +AzureAd = {} + +AzureAd.getConfiguration = function(returnNullIfMissing){ + var config = ServiceConfiguration.configurations.findOne({service: 'azureAd'}); + + if (!config && !returnNullIfMissing) + throw new ServiceConfiguration.ConfigError(); + else if (!config && returnNullIfMissing) + return null; + + //MUST be "popup" - currently Azure AD does not allow for url parameters in redirect URI's. If a null popup style is assigned, then + //the url parameter "close" is appended and authentication will fail. + config.loginStyle = "popup"; + + return config; +}; \ No newline at end of file diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index dc2b334..0000000 --- a/lib/config.js +++ /dev/null @@ -1,11 +0,0 @@ -AzureAd.getConfiguration = function(){ - var config = ServiceConfiguration.configurations.findOne({service: 'azureAd'}); - if (!config) - throw new ServiceConfiguration.ConfigError(); - - //MUST be "popup" - currently Azure AD does not allow for url parameters in redirect URI's. If a null popup style is assigned, then - //the url parameter "close" is appended and authentication will fail. - config.loginStyle = "popup"; - - return config; -}; diff --git a/lib/resources.js b/lib/resources.js index b74451b..de18571 100644 --- a/lib/resources.js +++ b/lib/resources.js @@ -1,27 +1,64 @@ AzureAd.resources = {}; -AzureAd.resources.collection = {} //create collection here that just stores names - -AzureAd.resources.registerResource = function(resourceUri) { +var resources = {}; +AzureAd.resources.registerResource = function(friendlyName, resourceUri){ + resources[friendlyName] = resourceUri; }; -AzureAd.resources.getOrUpdateUserAccessToken = function(resourceUri, user){ - //check for null user - - //check for un-authenticated user +AzureAd.resources.getOrUpdateUserAccessToken = function(friendlyName, user){ + checkResourceExists(friendlyName); + checkUserIsDefined(user); + ensureAzureAdResourcesOnUser(user, friendlyName); - //get token using refresh token + if (isAccessTokenMissingOrExpired(user, friendlyName)){ + var tokens = getTokensForResource(user, friendlyName); + saveTokensForUser(user, friendlyName, tokens); + } - //probably a good idea to include - //serviceData.resources - graph + office? + return user.azureAdResources[friendlyName].accessToken; +} - return AzureAd.http.getAccessTokensBase(resourceUri, { +function getTokensForResource(user, friendlyName){ + return AzureAd.http.getAccessTokensBase(resources[friendlyName], { grant_type: 'refresh_token', - refresh_token: refreshToken + refresh_token: user.services.azureAd.refreshToken }); } +function saveTokensForUser(user, friendlyName, tokens){ + user.azureAdResources[friendlyName] = tokens; + var modifier = { "$set" : {} }; + modifier["$set"]["azureAdResources." + friendlyName] = user.azureAdResources[friendlyName]; + + Meteor.users.update(user._id, modifier); +} + +function isAccessTokenMissingOrExpired(user, friendlyName){ + return !user.azureAdResources[friendlyName].accessToken || user.azureAdResources[friendlyName].expiresAt >= new Date(); +} + +function checkUserIsDefined(user) { + if (!user){ + throw new Meteor.Error("azure-active-directory:User required", "The supplied user is null or undefined"); + } +} + +function ensureAzureAdResourcesOnUser(user, friendlyName){ + if (!user.azureAdResources){ + user.azureAdResources = {}; + } + if (!user.azureAdResources[friendlyName]){ + user.azureAdResources[friendlyName] = {}; + } +} + +function checkResourceExists(friendlyName){ + if (!(friendlyName in resources)) { + var details = "Could not find a resource with the friendly name '" + friendlyName + "'."; + throw new Meteor.Error("azure-active-directory:Resource not registered", details); + } +} diff --git a/lib/serverHttp.js b/lib/serverHttp.js index 624e80d..a624e5f 100644 --- a/lib/serverHttp.js +++ b/lib/serverHttp.js @@ -7,7 +7,10 @@ AzureAd.http.call = function (method, url, options) { response = HTTP.call(method, url, options) } catch (err) { - var details = JSON.stringify({url : url}); + var details = JSON.stringify({ + url : url, + requestParams : options.params + }); throw new Meteor.Error("azure-active-directory:failed HTTP request", err.message, details); } @@ -46,7 +49,6 @@ AzureAd.http.getAccessTokensBase = function (resourceUri, additionalRequestParam resource: resourceUri }; var requestBody = _.extend(baseParams, additionalRequestParams); - var response = AzureAd.http.call("POST", url, { params: requestBody }); return { diff --git a/package.js b/package.js index a1ee420..69d2a3c 100755 --- a/package.js +++ b/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Azure Active Directory OAuth flow", - version: "0.2.5", + version: "0.3.0", name: "wiseguyeh:azure-active-directory", git: "https://github.com/djluck/azure-active-directory" }); @@ -14,10 +14,9 @@ Package.onUse(function(api) { api.use('accounts-base@1.1.3', ['client', 'server']); api.export('AzureAd'); - api.export('Graph'); - api.addFiles(['azureAd.js', 'config.js']); - api.addFiles(['resources.js', 'serverHttp.js', 'resources/graph.js'], 'server'); + api.addFiles(['lib/azureAd.js']); + api.addFiles(['lib/resources.js', 'lib/serverHttp.js', 'resources/graph.js'], 'server'); api.addFiles(['azure_ad_configure.html', 'azure_ad_configure.js'], 'client'); api.addFiles('azure_ad_server.js', 'server'); api.addFiles('azure_ad_client.js', 'client'); diff --git a/resources/graph.js b/resources/graph.js index c989ed6..493076d 100644 --- a/resources/graph.js +++ b/resources/graph.js @@ -1,6 +1,6 @@ AzureAd.resources.graph = {}; +AzureAd.resources.graph.friendlyName = "graph"; AzureAd.resources.graph.resourceUri = "https://graph.windows.net/"; -AzureAd.resources.graph.whitelistedFields = ['objectId', 'userPrincipleName', 'mail', 'displayName', 'surname', 'givenName']; AzureAd.resources.graph.getUser = function (accessToken) { var config = AzureAd.getConfiguration(); @@ -11,6 +11,6 @@ AzureAd.resources.graph.getUser = function (accessToken) { if (Meteor.isServer){ Meteor.startup(function(){ - AzureAd.resources.registerResource(AzureAd.resources.graph.resourceUri); + AzureAd.resources.registerResource(AzureAd.resources.graph.friendlyName, AzureAd.resources.graph.resourceUri); }); } diff --git a/todo.js b/todo.js deleted file mode 100644 index bb5426b..0000000 --- a/todo.js +++ /dev/null @@ -1,74 +0,0 @@ -//where do we store the access tokens for the different resources? -//a seperate collection? On the user? -//serviceData seems a good candidate... -//going to need to store different service configurations per token - -/* - A resource is an entity made up of the following values: - - resource (name) - - refresh_token - - access_token - - expires_on - - scope - - A resource should provide: - - isAccessTokenExpired() - - getOrRefreshAccessToken() - a mechanism to get access token (use refresh_token to automatically get an up-to-date token) - - - The library should provide - - The inbuilt resource, graph - - A method to register additional resources, specifying the order in which they are added - - Resources is one or more Resource entities, in a specified order. - - addResourceToTopOfHierarchy(r) - - addResourceToBottomOfHierarchy(r) - - getResources() - - how do we package the office 365 resource? As a standalone package - (this package does nothing but add the office 365 resource to the top of the hierarchy) - we then provide the office 365 api on top of this. - - - Office 365 isn't really a service, it's some additional properties: - - access token, refresh token, etc. - These need to be stored per user. We won't add any additional service config - we may need a new collection to keep all this stuff. It would probably be better to append this stuff to a user however. - resources per user- we could fetch these or add them in... not sure. - users is probably going to be the best place to stash this stuff rather than an individual collections. - - azureAdResources is added to the user when attempting to use office 365. - What about the graph resource? how do we add this in? Should we add this in? - Ignore it for now. - - azureAdResources.office365 will contain everything needed - AzureAdResources.graph could be added in as well... - how would we add graph details to the thing? tricky.. - - azureAdResources needs to be managed - - registering a resource (the resource uri) - - adding a resource for a user (adding it to the users azureAdResources collection) - - authenticating that resources for a user (getting access token - - for graph, we could watch when an account with the given service identifer was created, kill the watch and update the affected user - - when calling resources, service configuration will come into it. - - */ - - -var getCalendars = function (accessToken) { - var config = AzureAd.getConfiguration(); - var url = "https://outlook.office365.com/api/v1.0/me/calendarview?startdatetime=2015-04-01T23:00:00.000Z&enddatetime=2015-04-02T23:00:00.000Z"; - - var requestBody = { - headers: { Authorization : "Bearer " + accessToken} - } - try { - var response = HTTP.get(url, requestBody); - - } catch (err) { - throw getError("Failed to fetch identity from AzureAd", err.message, url, requestBody); - } -}; - -//"https://outlook.office365.com/" \ No newline at end of file