Configure Teams call forwarding policy via MS Graph API - microsoft-graph-api

TL;DR:
what's the MS Graph API for setting the teams call forwarding policy of an user?
Background:
Currently I'm trying to migrate a Lync/Skype-based .NET application to Teams.
The Teams related part is about setting the call forwarding preferences of a few specific users.
Those users have direct routing enabled, i.e. you can call a fixed PSTN/suffix number and the user will receive the call on his mobile. That mobile number is depending on the shift, so the programm is adapting it to whoever has the shift duty at that time.
What I've tried so far?
I can authenticate with the MS Graph API.
I know that there's TAC extension for this purpose ([1] and [2])
There's also a Powershell extension [3]
I'm not the first one to ask the question, but other threads usually got stuck [4]
The Call-Redirect is not what I want, as I'm not actively listening on those instances.
There's a github for Teams related scripts, but unfortunately without sources ...
I haven't yet reflected the Powershell extension
There is a promising user settings entry, where you can change shifts but not the call forwarding
Plan B?
invoke the powershell cmdlet, but that seems to be so 2000-ish
Update 2022-06-20
I'm starting to reflect the ps module. So API seems to be something like https://api.interfaces.records.teams.microsoft.com/Skype.VoiceGroup/userRoutingSettings/ + userId
The teams user id can be retrieved
Some parts of teams rely still on an older REST API (german only, sorry)
Update 2022-06-30
a POC which can be improved would look like this (... if I've packed into the usual AcquireTokenOnBehalfOf, then I'll add it as an answer ...)
Imports System.IO
Imports Microsoft.Identity.Client
Imports System.Globalization
Imports System.Net.Http
Imports System.Text
Imports System.Net.Http.Headers
Imports System.Net
Imports Newtonsoft.Json
Imports System.IdentityModel.Tokens.Jwt
Public Class LyncTest
Public Shared Sub Test()
Dim InstanceId As String = "https://login.microsoftonline.com/"
Dim RedirectURI As String = "https://login.microsoftonline.com/common/oauth2/nativeclient"
' Ids / Secrets and can be found on your Azure application page
Dim TenantId As String = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Dim AppId As String = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Dim secretVal As String = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Dim username As String = "xxxxxxx.xxxxxx#xxxxxxxxxxxx.com"
Dim password As String = "xxxxxxxxxxxx"
' Teams scope
Dim scope As String = "48ac35b8-9aa8-4d74-927d-1f4a14a0b239/.default"
Dim httpClient = New HttpClient()
' start resource owner password credential flow
' see https://learn.microsoft.com/en-us/powershell/module/teams/connect-microsoftteams?view=teams-ps#example-4-connect-to-microsoftteams-using-accesstokens
Dim baseParam As String =
$"client_id={AppId}" &
$"&username={WebUtility.UrlEncode(username)}" &
$"&password={WebUtility.UrlEncode(password)}" &
$"&grant_type=password" &
$"&client_secret={WebUtility.UrlEncode(secretVal)}" &
$"&scope={scope}"
' get user_impersonation token
Dim tokenReq As New HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/token") With {
.Content = New StringContent(baseParam, Encoding.UTF8, "application/x-www-form-urlencoded")
}
Dim TokenRes As HttpResponseMessage = httpClient.SendAsync(tokenReq).Result
Dim TokenObj As GraphToken = JsonConvert.DeserializeObject(Of GraphToken)(TokenRes.Content.ReadAsStringAsync.Result())
Dim JwtReader As New JwtSecurityTokenHandler
Dim JwtToken As JwtSecurityToken = JwtReader.ReadToken(TokenObj.AccessToken)
Dim UserOid As String = JwtToken.Payload("oid")
' set user calling routing
Dim RoutingURL As String = $"https://api.interfaces.records.teams.microsoft.com/Skype.VoiceGroup/userRoutingSettings/{UserOid}"
httpClient.DefaultRequestHeaders.Authorization = New AuthenticationHeaderValue("Bearer", TokenObj.AccessToken)
Dim RoutingJSON As String =
"{""sipUri"":""sip:andreas.beeker#kraiburg-tpe.com""," &
"""forwardingSettings"":{""isEnabled"":false,""forwardingType"":""Simultaneous"",""targetType"":""Unknown"",""target"":""""}," &
"""unansweredSettings"":{""isEnabled"":true,""targetType"":""SingleTarget"",""target"":""+491701234567"",""delay"":""00:00:20""}," &
"""callGroupDetails"":{""targets"":[],""order"":""Simultaneous""},""callGroupMembershipSettings"":{""callGroupMembershipDetails"":[]}}"
Dim RoutingReq As New HttpRequestMessage(HttpMethod.Post, RoutingURL) With {
.Content = New StringContent(RoutingJSON, Encoding.UTF8, "application/json")
}
Dim RoutingRes As HttpResponseMessage = httpClient.SendAsync(RoutingReq).Result
Console.WriteLine(If(RoutingRes.IsSuccessStatusCode, "success", "failed"))
End Sub
Public Class GraphToken
<JsonProperty(PropertyName:="access_token")>
Public Property AccessToken As String
<JsonProperty(PropertyName:="expires_in")>
Public Property ExpiresIn As Integer
<JsonProperty(PropertyName:="ext_expires_in")>
Public Property ExpiresInExt As Integer
<JsonProperty(PropertyName:="scope")>
Public Property Scope As String
<JsonProperty(PropertyName:="token_type")>
Public Property TokenType As String
End Class
End Class

There is no Graph API available for setting the teams call forwarding policy of an user.
Any user policy/tenant configuration are only exposed through PowerShell or admin portal now.

If you just want to change the forwarding status in Teams without catching callId and transferring individual calls, there is a simpler way by using PowerShell:
Install PowerShell module for M$Teams
https://www.powershellgallery.com/packages/MicrosoftTeams straight to PowerShell, for example by using
Install-Module -Name MicrosoftTeams -RequiredVersion 4.6.0 -AllowClobber
Assuming that you have permission set up forwarding by using Set-CsUserCallingSettings command, for example
Set-CsUserCallingSettings -Identity user#email.com -IsUnansweredEnabled $FALSE
Set-CsUserCallingSettings -Identity user#email.com -IsForwardingEnabled $true -ForwardingType Immediate -ForwardingTargetType SingleTarget -ForwardingTarget "+123456789"
In theory, only the second line is necessary, but I've noticed that PowerShell throws an error if Voicemail is enabled
This will change the forwarding status for all calls incoming to the selected identity. Note, that the user can change it still in Teams GUI.

Related

Retrieve file attachment from MMS via ASP.NET MVC controller

I'm trying out the Twilio service to interact with individuals via SMS/MMS. I've sorta figured out how to send MMS messages to initiate the "conversation" and that seems to be working well. However, now I'm trying to build a system to respond to the incoming messages on my SMS/MMS-enabled test number. I'm working from one of the examples I found on the Twilio documentation site to build an ASP.NET MVC web service to handle the conversation (VB.NET):
Imports System.Web.Mvc
Imports Twilio.AspNet.Common
Imports Twilio.AspNet.Mvc
Imports Twilio.TwiML
Namespace Controllers
Public Class SMSController
Inherits TwilioController
' GET: SMS
Function Index(ByVal IncomingSMS As SmsRequest) As TwiMLResult
Dim SMSResponse As New MessagingResponse
Dim SMSMessage As String = IncomingSMS.Body
Dim JediCode As String = "There is no emotion, there is peace."
Dim SithCode As String = "Peace is a lie. There is only Passion."
JediCode += vbCrLf & "There is no ignorance, there is knowledge."
JediCode += vbCrLf & "There is no passion, there is serenity."
JediCode += vbCrLf & "There is no chaos, there is harmony."
JediCode += vbCrLf & "There is no death, there is the Force."
SithCode += vbCrLf & "Through Passion, I gain Strength."
SithCode += vbCrLf & "Through Strength, I gain Power."
SithCode += vbCrLf & "Through Power, I gain Victory."
SithCode += vbCrLf & "Through Victory my chains are Broken."
SithCode += vbCrLf & "The Force shall free me."
If SMSMessage IsNot Nothing Then
If SMSMessage.ToUpper.Trim = "JEDI" Then
SMSResponse.Message(JediCode)
ElseIf SMSMessage.ToUpper.Trim = "SITH" Then
SMSResponse.Message(SithCode)
Else
SMSResponse.Message("Ahsoka? Is that you?")
End If
Else
SMSResponse.Message("What did you want to know?")
End If
Return TwiML(SMSResponse)
End Function
End Class
End Namespace
Yes, this is all just "play" stuff that I'm using for testing and will eventually be replaced with something more appropriate to the purpose, but I want to try and figure it all out before I get too deep into the reality of things.
I've set up the site on my IIS server, registered the DNS, and even gotten my SSL certificate set up. Everything seems to be working great with my simple testing so far, but there are a couple of things that I still haven't been able to figure out so far and I'm hoping someone here can point me in the right direction.
I'll ask each as a separate question, but here's the first: how do I retrieve the attachment from an MMS message?
I'd like to be able to receive PDFs (and possibly other file types) and pass them along via email to an appropriate individual or department. I know how to do the emailing, but I haven't been able to find appropriate documentation for how to retrieve the attachment(s) in the MMS message to actually include it in that email process.
When I try to access the properties of the IncomingSMS (SmsRequest) object, I don't find any reference to Media in any of them - no NumMedia, no MediaUri, nothing. There doesn't appear to be an MmsRequest object type (that I've found yet, anyway).
What am I overlooking here to be able to retrieve the PDF I sent to my test number for further processing? Should I change the method's definition to accept the object as a MessageResource or something?
EDIT: I forgot to mention that I checked the Twilio console and see that the message was apparently received successfully with the attachment, so I know that at least that part is working properly.
I've asked a second, related question that goes along with this one to help "finalize" some things for our goals.
The SmsRequest class that you're using comes from the Twilio helper library for ASP.NET which aims to make it easier to integrate Twilio into ASP.NET. However, the SmsRequest and other classes do not cover all possible webhook parameters. If there's parameters missing from the class, you can still retrieve the parameters manually instead of relying on MVC Model Binding.
Based on this C# sample, I created a VB.NET sample to show how to receive and save incoming MMS files:
Imports System.IO
Imports System.Net.Http
Imports System.Threading.Tasks
Imports MimeTypes
Imports Twilio.AspNet.Mvc
Imports Twilio.TwiML
Imports Twilio.TwiML.Messaging
Public Class HomeController
Inherits TwilioController
Shared httpClient As HttpClient = New HttpClient
Async Function Index() As Task(Of TwiMLResult)
Dim response As New MessagingResponse
Dim message As New Message
Dim numMedia = Short.Parse(If(Request.Form.Get("NumMedia"), 0))
If numMedia = 0 Then
response.Message("No file received.")
Return TwiML(response)
End If
For mediaIndex As Integer = 0 To numMedia - 1
Dim mediaUrl = Request.Form.Get($"MediaUrl{mediaIndex}")
Dim contentType = Request.Form.Get($"MediaContentType{mediaIndex}")
Dim saveFilePath = Server.MapPath(String.Format(
"~/App_Data/{0}{1}",
Path.GetFileName(mediaUrl),
MimeTypeMap.GetExtension(ContentType)
))
Await DownloadUrlToFileAsync(mediaUrl, saveFilePath)
Next
response.Message("File received.")
Return TwiML(response)
End Function
Private Async Function DownloadUrlToFileAsync(mediaUrl As String, saveFilePath As String) As Task
Dim Response = Await httpClient.GetAsync(mediaUrl)
Dim httpStream = Await Response.Content.ReadAsStreamAsync()
Using fileStream As Stream = IO.File.Create(saveFilePath)
Await httpStream.CopyToAsync(fileStream)
Await fileStream.FlushAsync()
End Using
End Function
End Class
You can probably simplify the code a little by assuming there's only one file going to be sent over MMS, but it's a good idea to handle other cases too.
To get the correct file extension, I'm using this
MimeTypeMap library, but you can roll your own solution.
By default the files from the incoming MMS are publicly available via the MediaUrl{mediaIndex} URL, but it's a good idea to turn on Basic Auth for these media files.
If you're turning on Basic Auth for media files, you'll need to add the authentication header to the HTTP requests, like this:
Imports System.IO
Imports System.Net.Http
Imports System.Net.Http.Headers
Imports System.Threading.Tasks
Imports MimeTypes
Imports Twilio.AspNet.Mvc
Imports Twilio.TwiML
Imports Twilio.TwiML.Messaging
Public Class HomeController
Inherits TwilioController
Shared httpClient As HttpClient = CreateHttpClient()
Private Shared Function CreateHttpClient() As HttpClient
Dim client As New HttpClient
Dim appSettings As NameValueCollection = ConfigurationManager.AppSettings
If Boolean.Parse(If(appSettings.Get("TwilioUseBasicAuthForMedia"), False)) Then
Dim authString = $"{appSettings.Get("TwilioAccountSid")}:{appSettings.Get("TwilioAuthToken")}"
authString = Convert.ToBase64String(Encoding.ASCII.GetBytes(authString))
client.DefaultRequestHeaders.Authorization = New AuthenticationHeaderValue("Basic", authString)
End If
Return client
End Function
...
End Class
I'm retrieving the TwilioUseBasicAuthForMedia, TwilioAccountSid, and TwilioAuthToken from the Web.config appSettings.
(Make sure you don't check those secrets into source control, and use UserSecretsConfigBuilder instead, to securely set the secrets).
Here's the source code on GitHub.
You're welcome to submit a GitHub issue and/or a PR to add support for these missing parameters to the SmsRequest. Tho, it wouldn't be as easy as normal model binding because the number of parameters increases as more files are sent over MMS.

Is it possible to generate IdTokens for AzureAd for writing End To End Tests with Protractor?

We're writing end to end tests with protractor for an application written in Angular. The app authenticates via Azure AD using the ADAL library.
Currently we click through the AzureAd sign in process using WebDriver before the tests start, but this is fragile. We had issues with the Microsoft password login screen not loading at all. Also the login process locally can get complicated because it takes our Windows credentials sometimes and sometimes it doesn't.
Looking for alternatives, is it possible to generate either:
An idtoken in the protractor start up process (onPrepare) through some node library (like passport?) from Azure Ad directly? That I can later set for the tests to use.
Some sort of forever living static bearer idtoken that the e2e tests read from a config file?
Is there any other approach I could be missing?
You can use the ROPC flow to acquire tokens for tests.
This is one of the cases where using this flow can be ok in my opinion.
Using it to bypass the login screen in a normal app is not okay.
It also doesn't work with all kinds of users, federated users can sometimes work, depending on how it's setup.
Here is what I've used:
// _authority = e.g. https://login.microsoftonline.com/company.com
string tokenUrl = _authority + "oauth2/v2.0/token";
var req = new HttpRequestMessage(HttpMethod.Post, tokenUrl)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = settings.ClientId, // client id for your app
["client_secret"] = settings.ClientSecret, // client secret for your app
["scope"] = $"openid profile {_resourceUri}/.default", // here you define what you want tokens for
["username"] = settings.UserName,
["password"] = settings.Password
})
};
HttpResponseMessage res = await _client.SendAsync(req);
string json = await res.Content.ReadAsStringAsync();
AadTokenResponse tokenResponse = JsonConvert.DeserializeObject<AadTokenResponse>(json);
// tokenResponse.IdToken contains the ID token
// The AadTokenResponse class:
class AadTokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("id_token")]
public string IdToken { get; set; }
}
The scope is where you must specify at least the openid scope in order to get an ID token.
Here I have also specified an API app ID URI + "/.default" to get an access token for an API.
You can keep the scope as openid profile if you only need the id token.
Keep in mind that you will then need to secure app credentials and user credentials in your test configuration.
Also, I recommend that these credentials are not for a production environment.
You may need to create cloud-only user accounts for them to work properly.
Also the user accounts cannot have MFA enabled.
If their password expires, you'll need to set a new password and update your test config.

Exchange Webservice using Oauth throws error when subscribing a resource

I am using OAuth2.0 to connect to Exchange webservices. Everything else seems to work ok for me . However when i try to subscribe one of the room resource by using grouping info and providing the anchor mailbox as one of the primary mail box it throws an error.
"Request failed because EWS could not contact the appropriate CAS server for this request."
So for example i am trying to subscribe nitroom1 and one the primary mailbox associated with the group is nitroom2 which i am using as X-AnchorMailbox then i got the above error.
public static ExchangeService GetExchangeService(string exchangeURL, string userName, string password, string resourceEmail, string primaryMailbox, string clientID, string tenantID, string clientSecret, string certName)
{
ExchangeService service;
service = new ExchangeService(setTZtoUTC);
service.Url = new Uri(exchangeURL);
if (!string.IsNullOrWhiteSpace(clientID) && !string.IsNullOrWhiteSpace(tenantID))
{
string oAuthToken = multiExchangeManager.getOAuthTokenFromCache(clientID, tenantID, clientSecret, certName);
service.Credentials = new OAuthCredentials(oAuthToken);
}
else
{
service.Credentials = new WebCredentials(userName, password);
}
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, resourceEmail);
service.HttpHeaders.Add("X-AnchorMailbox", primaryMailbox);
service.HttpHeaders.Add("X-PreferServerAffinity", "true");
return service;
}
However if i connect ews using impersonate account then do same thing it works fine.
Also, if i use resourceMailbox same as primary mailbox then it works ok as well.so in my example it will look like this.
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "nitroom1");
service.HttpHeaders.Add("X-AnchorMailbox", "nitroom1");
This is how i am trying to use subscription.
exchangeService.SubscribeToStreamingNotifications(
new FolderId[] { WellKnownFolderName.Calendar, WellKnownFolderName.DeletedItems },
EventType.Created, EventType.Deleted, EventType.Modified, EventType.Moved, EventType.Copied);
Does anyone have any idea why its happening or what i am doing wrong here?
one more thing to add, i tried EWSEditor tool which provides subscription info and both above mentioned resources sharing same grouping info.
I think i found a solution for this issue, i just need to set
X-BackEndOverRideCookie with any service used for subscribing child mailbox.
For more info read this article
https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-maintain-affinity-between-group-of-subscriptions-and-mailbox-server

Clarification required about PrincipalContext security permissions and PrincipalContext's ContextType.Machine

using (PrincipalContext Context = new PrincipalContext(ContextType.Domain, DomainURL, UserName, Password))
{
UserPrincipal Account = new UserPrincipal(Context);
Account.GivenName = strFirstName;
Account.Surname = strLastName;
PrincipalSearcher srch = new PrincipalSearcher(Account);
foreach (var principal in srch.FindAll())
{
var p = (UserPrincipal)principal;
String FirstName = p.GivenName;
String LastName = p.Surname;
}
}
If i use the code above to query Active Directory and the UserName(account) passed in the PrincipalContext constructor is in a domain that has no trust with the target domain(domain to be queried), i get the below error.
System.DirectoryServices.AccountManagement.PrincipalServerDownException: The server could not be contacted. ---> System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
Would i be correct to assume that if the PrincipalContext construct was changed to,
using (PrincipalContext ctx = new PrincipalContext(ContextType.Machine))
the code would execute successfully as long as the client is in the target domain?
Lets assume the first code with UserName and Password was called by a client in domain A trying to search for user info in domain B, here establishing context failed because the account used is in domain A that has no trust with domain B.
am i correct to assume that if i change the ContextType to Machine, and the client calling the code is in domain B, the code would execute succefully?
No, that would not be a correct assumption. ContextType.Machine means that you want to work with local accounts.
Your PrincipalSearcher will end up searching the local SAM database rather than Active Directory

Authorizing Google API using stored refresh token and iDataStore in VB.NET

I am developing an integration with Google Calender from a vb.net web application. So far I understand that I need my own implementation of iDataStore as I will be storing refresh tokens belonging to different users on my website with the view to connect to their calendar later and add events.
Here's my code for creating a new authorised user:
Dim clientSecrets As New ClientSecrets
clientSecrets.ClientId = "MyClientID"
clientSecrets.ClientSecret = "MyClientSecret"
Dim input() As String = {"https://www.googleapis.com/auth/calendar"}
Dim scope As New List(Of String)(input)
Dim myStoredResponse As New GoogleOauthAPI.StoredResponse(txtrefreshToken.Text)
Dim savedDataStoreObj As New GoogleOauthAPI.SavedDataStore(myStoredResponse)
Dim credential As UserCredential = GoogleWebAuthorizationBroker.AuthorizeAsync(clientSecrets, scope, "user", Threading.CancellationToken.None, savedDataStoreObj).Result
I am getting the following error when attempting to create the UserCredential object:
Value cannot be null. Parameter name: task
The GoogleOauthAPI.StoredResponse was based on the following guide but converted from C# to vb.net. I had some uncertainty as to whether the conversion of the code was done correctly.
Here is the original C# class
and here is the class converted to vb
When the myStoredResponse object is created I can inspect it in debug mode and it looks like it's pulling in the refresh code, I'm just uncertain why the user credentials cannot be created and throws this error?
Any help would be much appreciated!
Solved the problem in the end. When converting the iDataStore interface class from C# to VB.net I did not have the Implements IDataStore.MethodName appended to each function.
Here is my revised iDataStore VB class, feel free to use this in your own projects.

Resources