Category: Blog
Microsoft Azure Compliance Offerings
Link to Microsoft Azure Compliance Offerings. Appendices start at page 63.
Azure Services in FedRAMP and DoD SRG Audit Scope – Azure Government | Microsoft Docs
Link to Azure Services in FedRAMP and DoD SRG Audit Scope. This section goes over offerings per the following audit scopes:
- DoD CC SRG IL 2
- DoD CC SRG IL 4
- DoD CC SRG IL 5 (Azure Gov)*
- DoD CC SRG IL 5 (Azure DoD) **
- FedRAMP High
- DoD CC SRG IL 6
CC SRG IL stands for Cloud Computing Security Requirements Group Impact Level. These levels are defined as follows:
How to externally authenticate and consume data from Dynamics 365 CE Web API
There are 4 steps to authenticate and consume data from the D365 CE Web API in the cloud. In this scenario, we are going to use what is called the OAuth 2.0 Authorization Code Flow. It is one of the ways you can authenticate your calls. Authorization code flow is good because it’s hands-off. I will be using Postman to demonstrate these steps. The steps are:
- Authorize using password grant type.
- Get Token
- Use Refresh Token to get new Token (this will be used to get the token when it expires)
- Make a request to consume data
Step 1: Authorize using password grant type to get impersonation token.
Make a GET request to https://login.microsoftonline.com/common/oauth2/token with the header “content-type” = “application/x-www-form-urlencoded”. See Figure 1.
The body will include the client_id, resource, username, grant_type, client_secret, and password (See Figure 2). You can get the client_id and client_secret from the Azure Active Directory from an app that is authorized to access PowerPlatform, CDS, or Dynamics. The grant_type will be password.
You will get the access_token and refresh_token in response to this call. Note that the token will expire in 3599 seconds, which is 1 hour. This is where the refresh token will come in.
Step 2: Get the Token
The next step is to use the access token to get access token to consume the data. Make a POST request to:
https://login.microsoftonline.com/[[TENANTID]]/oauth2/token.
Note: You can get the tenant ID from Azure AD or from the login url when you’re authenticating into Microsoft products.
Get the access token from the first call and put it into the authorization field in Postman with TYPE being Bearer token. This will create a header that’s “authorization” : “Bearer [token]”. See Figure 3
Header remains content-type: application/x-www-form-urlencoded. See Figure 4.
The body this time will have the client_id, client_secret, grant_type of client_credentials, resource (url of your instance) and your tenant_id.
The response will include an access token you can use to consume data from your resource. This is different from the access_token we received earlier because the scope of that token was only user_impersonation. This token will also expire in 1 hour. In the next step we will see how to get a new token from the refresh token.
Step 3: Get a new token using the refresh token
At this point, you can consume data from the Web API using the access token from step 2, but it will expire in 1 hour and if you want to keep getting a new password every hour, you’ll have to hard code your password in step 1. We want to avoid putting our passwords in places so we will use the refresh token to get a new password before it expires in the allotted 1 hour.
So we make a POST request to https://login.microsoftonline.com/[TENANTID]/oauth2/v2.0/token. The authorization type is bearer and we use the same access token from step 2. This will add a header “authorization”:”Bearer [token]” to the request. See figure 7.
The body includes client_id, resource, refresh_token, and secret (See figure 8). Note: we got the refresh token in step 1. Use this refresh token to get a new access token before it expires, and you won’t need to put step 1 in your code.
You will get a response with a new access token and refresh token with a 1-hour expiration time. No username or password required.
Step 4: Consume data
You can now continuously consume data from the web API using the access_token. Make a GET request to the resource you want. In my case, I will be pulling the top ten accounts. The authorization will be the Bearer Token (access_token from step 3). See figure 10.
You won’t need any specific headers besides the bearer token authorization which is being generated from the authorization above, but you can put in “content-type” of “application/json”, or other recommended OData headers if you want. Run the request and you’ll get your consumable data (See figure 11). You can also call actions or do CRUD operations with this token. Just make sure to refresh it before it expires.
Open model-driven forms in “application mode”
I recently had a customer ask if they could replace InfoPath forms with Canvas Apps. That is a perfect use for canvas apps, but the users of those forms already had licenses to CE, so it got me thinking about using your regular model-driven forms as something that could pop up from an internal SharePoint site or an intranet.
Turns out it can simply be achieved through the removal of the command bar and navbar from the URL, and then the removal of the title bar and toolbar from the window.open function. A sample script is as follows.
window.open('https://[COMPANY].crm.dynamics.com/main.aspx?appid=00000000-0000-0000-0000-000000000000&cmdbar=false&forceUCI=1&navbar=off&pagetype=entityrecord&etn=contact','winname',"directories=0,titlebar=0,toolbar=0,location=0,status=0,menubar=0,scrollbars=no,resizable=no,width=1200,height=900");
The most used code snippets in Dynamics365 – Rk’s CRM
Close Opportunity in D365 CE with Alert.js 2.1
There is an article on Magnetism about how to use Alert.js 3.0 to mimic an opportunity close dialog. This is an example of how to use the open-source Alert.js 2.0 to mimic the opportunity close function. Alert.js 3.0 is $1200 and I definitely recommend it if you have the budget, but if not, you can still use 2.0 to do achieve similar functionality
So without further chit chat, here is what you do.
- Download and install Alert.js 2.1 on your CRM instance.
- Put the Opportunity entity in a solution without any sub-components.
- Create a JavaScript web resource and call it OpportunityClose.js or whatever you want to call it.
- Put the code listed below in the OpportunityClose.js
- Download and open Ribbon Workbench in XrmToolBox or your CRM instance.
- Add a button as seen in Image 1.
- Add a command as seen in Image 2.
- Create an Enable Rule and add it to the command as seen in Image 3.
// Called from ribbon button, passing through first primary record ID
function showCloseAsWon(firstPrimaryItemId, primaryControl) {
var context = primaryControl.context;
var pid = firstPrimaryItemId.replace( /[{}]/g, '' );
var title = "Close Opportunity";
debugger;
//Build Status Reason Option Set values
var options = "";
var req = new XMLHttpRequest();
req.open("GET", context.getClientUrl() + "/api/data/v9.1/stringmaps?$select=attributevalue,value&$filter=attributename eq 'statuscode' and objecttypecode eq 'opportunity'", false);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
req.onreadystatechange = function() {
if (this.readyState === 4) {
req.onreadystatechange = null;
if (this.status === 200) {
var results = JSON.parse(this.response);
for (var i = 0; i < results.value.length; i++) {
var attributevalue = results.value[i]["attributevalue"];
var value = results.value[i]["value"];
var opt = "<option "+"id='" + value +"' value='"+ attributevalue.toString() +"'>"+ value +"</option>";
options = options.concat(opt);
}
} else {
options = "<option value='5'>Lost</option>";
}
}
};
req.send();
var style = "<style>"
+ ".header { padding-top: 10px;}"
+ "p { font-size: 14px; }"
+ ".alert-label { line-height: 22px; margin-top: 8px; } "
+ ".alert-textarea { width: 76%; height: 160px; border:1px solid #AAA; padding: 2%; } "
+ ".alert-select { padding: 4px 8px; width: 60%; } "
+ ".alert-button { padding: 10px 20px; font-size: 14px; background-color: white; border: 1px solid #AAA; margin: 10px 10px 0 0; min-width: 100px; } "
+ ".alert-button:hover { background-color: #EEE; } "
+ ".alert-button:focus { background-color: #DDD; } "
+ "</style>";
var subtitle = "<div class='header'>"
+ "<p><strong>Provide the following information about why this opportunity is being closed.</strong></p>"
+ "</div>";
var status = "<div class='container'>"
+ "<div id='stdiv'><p class='alert-label'>Status</p>"
+ "<div><select class='alert-select' name='status' id='st' onchange='javascript: var s=1==this.value?document.querySelector("#srdiv").style.display=\"none\":document.querySelector(\"#srdiv\").style.display=\"inherit\";' onfocusout='javascript: statusValue = this.value;'>"
+ "<option id='Lost' value='2'>Lost</option>"
+ "<option id='Won' value='1'>Won</option>"
+ "</select></div></div>"
+ "<input type='hidden' value=" + pid + " id='op'></input>";
var reason1 = "<div id='srdiv'><p class='alert-label'>Status Reason</p>"
+ "<div><select class='alert-select' name='selLoseStatus' id='sr' onfocus='javascript: reasonValue = this.value;' onfocusout='javascript: reasonValue = this.value;'>";
var reason2 = "</select></div></div>";
var description = "<div id='dsdiv'><p class='alert-label'>Description</p>"
+ "<div><textarea class='alert-textarea' id='ds' onfocus='javascript: descValue = this.value;' onfocusout='javascript: descValue = this.value;' placeholder='Enter description here'></textarea></div>"
+ "</div>"
+ "<div id='alert-message'></div></div>";
var close1 = "<button onclick=\"javascript: ";
var clop = "var context,st=document.querySelector('#st').value,sr=1==st?-1:document.querySelector('#sr').value,ds=document.querySelector('#ds').value,id=document.querySelector('#op').value,op=1==st?'Win':'Lose',opportunityclose={'opportunityid@odata.bind':'/opportunities('+id+')',description:ds},parameters={OpportunityClose:opportunityclose,Status:sr};context='function'==typeof GetGlobalContext?GetGlobalContext():Xrm.Page.context;var req=new XMLHttpRequest;req.open('POST',context.getClientUrl()+'/api/data/v9.1/'+op+'Opportunity',!1),req.setRequestHeader('OData-MaxVersion','4.0'),req.setRequestHeader('OData-Version','4.0'),req.setRequestHeader('Accept','application/json'),req.setRequestHeader('Content-Type','application/json; charset=utf-8'),req.onreadystatechange=function(){if(4===this.readyState)if(req.onreadystatechange=null,204===this.status)Xrm.Page.data.refresh(!0);else{var e=this.responseText;console.log(e)}},req.send(JSON.stringify(parameters));"
+ "Alert.hide();";
var close2 = "\" value='Close' class='alert-button' name='Close Opportunity' aria-label='Close Opportunity'>Close Opportunity</button>";
var notnow = "<button onclick='javascript: Alert.hide()' class='alert-button' name='Not Now' aria-label='Not Now'>Not Now</button>";
var message = style + subtitle + status + reason1 + options + reason2 + description + notnow + close1 + clop + close2;
var buttons = [
]
var width = 480;
var height = 540;
var baseUrl = context.getClientUrl();
var preventCancel = false;
var icon = "INFO";
var padding = 30;
Alert.show(title, message, buttons, icon, width, height, baseUrl, preventCancel, padding);
}