Allow anyone to join or start microsoft teams meeting - azure-active-directory

I have registered an application using Microsoft's developer program. The application is supposed to create MS Teams meeting URLs for any tenant. Below is my current flow:
User from tenant/organization X (let's call him UserX) authorizes my app and gives permission to two scopes: User.Read and OnlineMeetings.ReadWrite.
UserX creates a MS Teams URL.
UserX shares the generated URL with the participants, where the user is not required to join the meeting.
Problem I'm facing: When anyone(both personal or any other organization/tenant account) tries to join the URL shared by UserX, they are not able to start or join the meeting. Microsoft says,
When the meeting starts, we'll let people know you're waiting
The only way I can start the meeting is when anyone from tenant/organization X signs-in and starts the meeting. I am even passing lobbyBypassSettings but have got no success.
Below is my code in python using msal.
def schedule_interview():
token = get_auth_token()
client_instance = retrieveClientInstance()
refresh_token = client_instance.acquire_token_by_refresh_token(
refresh_token=token.refresh_token,
scopes=['User.Read','OnlineMeetings.ReadWrite']
)
headers = {'Authorization': 'Bearer ' + refresh_token['access_token'], 'Content-Type': 'application/json'}
meeting_data = {
"subject": "My Awesome Meeting",
"lobbyBypassSettings":{ "scope": "everyone"}
}
response = requests.post('https://graph.microsoft.com/v1.0/me/onlineMeetings', json=meeting_data,headers=headers)
data = response.json()
return data['joinWebUrl']

Related

Forbidden (CSRF cookie not set.): /api/signinUser

The error occurs in this react code
const headers = new Headers({
"X-CSRFToken": Cookies.get('csrftoken')
});
const response = await fetch("api/signinUser",
{
method: "POST",
headers: headers,
body: formData
});
Trying to access this Django Api
#ensure_csrf_cookie
def signin(request):
if request.method == 'POST':
auth = False
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
print("Authenticating User", user)
if user is not None:
auth = True
login(request, user) # Does this return anything?
ret = {
"auth": auth
}
print("RET", ret)
return JsonResponse(ret)
I have django.middleware.csrf.CsrfViewMiddleware in my MIDDLEWARE variable
I'm running my Django server in an AWS EC2 instance that I access with http://<my public ip>:8000/
headers: {
'X-Requested-With': 'XMLHttpRequest',
'csrftoken': Cookies.get('csrftoken'),
},
As you are accessing using HTTP (and not https) you need to ensure the cookies are not https only (i.e. "Secure").
The "easiest" for this it to changes your settings to ensure that http cookies will work (https://docs.djangoproject.com/en/4.1/ref/settings/#csrf-cookie-httponly, https://docs.djangoproject.com/en/4.1/ref/settings/#csrf-cookie-secure and https://docs.djangoproject.com/en/4.1/ref/settings/#session-cookie-httponly).
However, as you're on AWS, it's fairly easy and cheap to access over HTTPS with a valid certificate if you have a (sub)domain name you can use.
Create yourself a Load Balancer (in the EC2) panel). That prompts you to...
Create a "Target Group". The target group contains the instance above
Set it so that a listener on "443" will redirect traffic to "80" on your instance (so your instance does not need a certificate). In doing this, you'll be prompted to create a certificate within AWS.
Point your DNS to the load balancer.
Please check costs, but the normally-expensive part (the certificate) is free, and you can set security groups to lock users our of having direct access to your EC2.
You should check out the docs.
https://docs.djangoproject.com/en/4.1/ref/csrf/
A hidden form field with the name ‘csrfmiddlewaretoken’, must be present in all outgoing POST forms.
They are referring to a hidden input field within your form.
For all incoming requests that are not using HTTP GET, HEAD, OPTIONS or TRACE, a CSRF cookie must be present, and the ‘csrfmiddlewaretoken’ field must be present and correct. If it isn’t, the user will get a 403 error.

How Do I Authenticate a Service Account via Apps Script in order to Call a GCP Cloud Function

I'm trying hell hard to run a GCP Cloud Function from Apps Script. The Cloud function requires Authentication. However I keep getting thrown a "401 Error"
On My Google Project;
I've Created a Cloud function that requires Authentication
I've Created a Service Account that has Invoke (and edit) access to that function
I've Downloaded the JSON key for that service account and saved it as an object named CREDS in my Apps Script
This is my script so far:
const CREDS = {....JSON Key I downloaded from Cloud Console}
function base64Encode(str){
let encoded = Utilities.base64EncodeWebSafe(str)
return encoded.replace(/=+$/,'')
}
function encodeJWT(){
const privateKey = `Copied the PK from the CREDs file and replaced all the escaped whitespace as a string literal`;
let header = JSON.stringify({
alg: "RS256",
typ: "JWT",
});
let encodedHeader = base64Encode(header);
const now = Math.floor(Date.now() / 1000);
let payload = JSON.stringify({
"iss": "https://accounts.google.com",
"azp": "OAUTH CLIENT ID I CREATED ON GCP",
"aud": "OAUTH CLIENT ID I CREATED ON GCP",
"sub": CREDS.client_id,
"email": CREDS.client_email,
"email_verified": true,
// "at_hash": "TMTv8_OtKA539BBRxLoTBw", //Saw this in a reverse engineered Token but didnt know what to put
"iat": now.toString(),
"exp": (now + 3600).toString()
})
let encodedPayload = base64Encode(payload);
let toSign = [encodedHeader, encodedPayload].join('.')
let signature = Utilities.computeRsaSha256Signature(toSign, privateKey)
let encodedSignature = base64Encode(signature);
let jwt = [toSign, encodedSignature].join('.')
return jwt;
}
function testFireStore() {
let funcUrl = "https://[MY PROJECT].cloudfunctions.net/MyFunc"
const token = encodeJWT()
let options = {
headers:{
"Authorization": "Bearer " + token
}
}
let func = UrlFetchApp.fetch(funcUrl,options)
Logger.log(func.getContentText())
}
The actual Cloud func just gives a "Hello World" for now and it tests fine in the console
FYI, some steps I've done already
I've generated a token using gcloud on my local machine and used it in my apps script, that works fine
I've taken the said token and reverse engineered it on https://jwt.io
I've used the code here to create my JWT function which I checked back with https://jwt.io to ensure its in the correct format.
This Solution posted by #TheMaster in the comments to my solution solved the issue.
On the GCP side, I went in and enabled compute Engine and App Engine, then used this solution and it worked.
The only odd thing is that the target_audience requested there, I had to do a bit of reverse engineering to get it. I had to get the Identity Token from the command line tool, then use jwt.io to decode it, getting the AUD key...
but that aside, everythign worked like a charm

how does get_current_user work

I'm really confused how Google App Engine's User's get_current_user() works. I've looked around the internet at a bunch of different guides and tutorials about login and authentication, and many of them mention similar methods.
If there are a million users logged in to my application at the same time, how can that method possibly work? Does each user get their own instance of the server? How does the server know which client it is talking to?
It doesn't make sense to me at all.
When logging in (by clicking on the URL generated by create_login_url()) a cookie containing user identifying information is prepared and pushed on the client side, then used in subsequent requests until the user logs out or the cookie expires. Calling get_current_user() simply checks the cookie existance/information and responds accordingly.
On the development server the cookie is named dev_appserver_login. I can no longer check the cookie name on GAE as I switched away from the Users API.
The actual handling of the cookie seems to happen somewhere on the Users service backend, for example, by looking at the google/appengine/api/users.py file in the python SDK:
def create_login_url(dest_url=None, _auth_domain=None,
federated_identity=None):
...
req = user_service_pb.CreateLoginURLRequest()
resp = user_service_pb.CreateLoginURLResponse()
try:
apiproxy_stub_map.MakeSyncCall('user', 'CreateLoginURL', req, resp)
...
The end point (at least for the development server) seems to somehow land somewhere in google/appengine/tools/appengine_rpc.py, for example:
#staticmethod
def _CreateDevAppServerCookieData(email, admin):
"""Creates cookie payload data.
Args:
email: The user's email address.
admin: True if the user is an admin; False otherwise.
Returns:
String containing the cookie payload.
"""
if email:
user_id_digest = hashlib.md5(email.lower()).digest()
user_id = "1" + "".join(["%02d" % ord(x) for x in user_id_digest])[:20]
else:
user_id = ""
return "%s:%s:%s" % (email, bool(admin), user_id)
def _DevAppServerAuthenticate(self):
"""Authenticates the user on the dev_appserver."""
credentials = self.auth_function()
value = self._CreateDevAppServerCookieData(credentials[0], True)
self.extra_headers["Cookie"] = ('dev_appserver_login="%s"; Path=/;' % value)

Azure Graph service not finding newly created user

I have a web application that uses Azure ACS and Azure AD to handle our authentication.
We have a user management feature in the web application that allows a user to create new users. This takes the details such as username, password, email etc. and uses the graph service to create a user in azure.
var newUser = new Microsoft.WindowsAzure.ActiveDirectory.User
{
userPrincipalName = user.UserName,
mailNickname = user.MailNickname,
accountEnabled = true,
displayName = user.FirstName + " " + user.Surname,
givenName = user.FirstName,
surname = user.Surname
};
newUser.passwordProfile = new PasswordProfile
{
forceChangePasswordNextLogin = false,
password = user.Password
};
var graphService = GetGraphService(tenantName);
graphService.AddTousers(newUser);
graphService.SaveChanges();
We are then required to create a record in the web application database for this user. The record needs the object ID from azure. So we use the graphService to get the newly-created user details. This is where my problem lies. It doesn't find the user.
private string GetObjectIdFromAzure(string userName, string tenantName)
{
var graphService = GetGraphService(tenantName);
var users = graphService.users;
QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User> response;
response = users.Execute() as QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User>;
var user = response.FirstOrDefault(x => x.userPrincipalName == userName);
return user != null ? user.objectId : "";
}
My code was working without any issues for a few months and only today I am having issues. What frustrates me more it that I have another deployment of the same code where it works without any issues. Some differences between the two deployments are:
The deployments use different Access control namespaces in Azure
The deployments have separate applications in Azure AD
One is https, one is http
The users for both system are under the same Directory.
I have put in logging in both deployments to get the number of users returned by
users.Execute()
In both systems it reported 100 (they share the same users)
Any ideas of what would cause this to stop working? I didn't change any code relating to this recently, I haven't changed any configuration on Azure and I didn't change the web.config of the application
The problem was caused by the fact that I was filtering the users after retrieving them. The graph API was only returning a maximum of 100 users.
So the process was like so:
User created in Azure
Success message returned
Web App searches Azure for user to get Object ID
Graph Api only returns top 100 users. User was not in top 100 alphabetically so error thrown
The reason it was working on our second deployment was that I was prefixing the user name with demo_ (we use this site to demo new features before releasing). This meant that it was being returned in the top 100 users.
I changed the code as follows so it filters during the retrieval instead of after:
private Microsoft.WindowsAzure.ActiveDirectory.User GetUserFromAzure(string userName, string tenantName, out DirectoryDataService graphService)
{
graphService = GetGraphService(tenantName);
var users = (DataServiceQuery<Microsoft.WindowsAzure.ActiveDirectory.User>)graphService.users.Where(x => x.userPrincipalName == userName);
QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User> response;
response = users.Execute() as QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User>;
var user = response.FirstOrDefault();
return user;
}

How to get Google profile info including custom fields from an Apps Domain user?

Using the user.profile and user.email scope and the /oauth2/v2/userinfo feed doesn't seem to return any custom fields (in my case Department) or phone numbers. These fields show up in the Domain Shared Contacts directory.
Is there perhaps an Apps Domain specific feed URL something like /oauth2/{DOMAIN}/v2/userinfo ?
Does the API/Service not support any custom fields yet?
Is there a way to fudge this into working?
Read access to your own Apps Domain Shared Contacts profile that's connected to your account shouldn't be so difficult.
I'd prefer a non-admin solution because my domain uses Common Access Cards w/ SAML authentication so I can't just store admin credentials (user : password) in an App Engine app and access the /m8/ feed. If there's a flow to access Domain Shared Contacts (with custom fields) with a beforehand authorized consumer key and secret I'd be interested in the instructions for getting that to work.
EDIT Jay Lee nailed it "https://www.google.com/m8/feeds/gal/{domain}/full"
Here's the proof of concept script using Google Apps Script (I'll add the final OAuth2 version when I finish it)
function getGal(email, passwd, domain) {
var res = UrlFetchApp.fetch("https://www.google.com/accounts/ClientLogin", {
contentType: "application/x-www-form-urlencoded",
method: "post",
payload: { "Email": email, "Passwd": passwd, "accountType": "HOSTED", "service":"cp" }
});
var auth = res.getContentText().match(/Auth=(.*)/i)[1];
Logger.log("Auth: " + auth);
res = UrlFetchApp.fetch("https://www.google.com/m8/feeds/gal/" + domain + "/full", {
method: "get",
headers: { "Authorization": "GoogleLogin auth=" + auth, "GData-Version": "1.0" }
});
Logger.log(res.getHeaders());
Logger.log(res.getContentText());
}
EDIT 2 OAuth version that returns JSON and only the info for the user accessing the script.
function googleOAuthM8() {
var oAuthConfig = UrlFetchApp.addOAuthService("m8");
oAuthConfig.setRequestTokenUrl('https://www.google.com/accounts/OAuthGetRequestToken?scope=https://www.google.com/m8/feeds/');
oAuthConfig.setAuthorizationUrl('https://www.google.com/accounts/OAuthAuthorizeToken');
oAuthConfig.setAccessTokenUrl('https://www.google.com/accounts/OAuthGetAccessToken');
oAuthConfig.setConsumerKey('anonymous');
oAuthConfig.setConsumerSecret('anonymous');
return {oAuthServiceName:"m8", oAuthUseToken:'always'};
}
function getGal(domain) {
res = UrlFetchApp.fetch("https://www.google.com/m8/feeds/gal/" + domain + "/full?alt=json&q=" + Session.getActiveUser().getEmail(), googleOAuthM8());
Logger.log(res.getHeaders());
Logger.log(res.getContentText());
}
Any non-admin user can access the GAL programmatically, see:
https://github.com/google/gfw-deployments/blob/master/apps/shell/gal/gal_feed.sh
I don't believe this API call is documented or supported officially but it works even with OAuth authentication rather than the example's ClientLogin (tested on the OAuth 2.0 playground with a non-admin user and the standard https://www.google.com/m8/feeds/ Contacts scope).
Note that the Global Address List is a compilation of user profiles, groups and shared contacts. You'll need to parse it out to find the user(s) you wish to get department information for.
I would utilize the Google Apps Profiles API to do this. It'll give you a bunch of meta information, including profile data and even profile photos:
https://developers.google.com/google-apps/profiles/
Even if you're using PIV/CAC/SAML, you will be able to auth using Two-Legged-OAuth.
https://developers.google.com/accounts/docs/OAuth#GoogleAppsOAuth
Two-legged-oauth is the path of least resistance, but you should also take a look at OAuth2, especially the JWT-signed service accounts portion -- however, it can be a little tricky to get working with the older GData xml apis.
As far as fields available go, you'll have to work with the ones on this page. There are extended properties where you add in arbitrary data, but they don't show up in the Contacts browser with Google Mail itself:
https://developers.google.com/gdata/docs/2.0/elements#gdProfileKind
On a sidenote, if you're in an LDAP environment (and since you mentioned CAC, I think you probably are), you should take a look at Google Apps Directory Sync, which can synchronize that profile data with your local AD/LDAP.
source: I deployed Google Apps to large organizations (3000+), public and private.
I have used the following approach with TwoLeggedOAuthHmacToken:
Consumer key and secret can be found in google apps admin dashboard
CONSUMER_KEY = 'domain.com'
CONSUMER_SECRET = 'secret_key'
class ContactClient():
def __init__(self, username):
# Contacts Data API Example ====================================================
self.requestor_id = username + '#' + CONSUMER_KEY
self.two_legged_oauth_token = gdata.gauth.TwoLeggedOAuthHmacToken(
CONSUMER_KEY, CONSUMER_SECRET, self.requestor_id)
self.contacts_client = gdata.contacts.client.ContactsClient(source=SOURCE_APP_NAME)
self.contacts_client.auth_token = self.two_legged_oauth_token
def newuser(self, username):
self.contacts_client.auth_token.requestor_id = username + '#' + CONSUMER_KEY
def getContacts(self, username=None):
if username:
self.newuser(username)
return self.contacts_client.GetContacts()
class MainPage(webapp2.RequestHandler):
def get(self):
contacts = ContactClient(username='username')
feed = contacts.getContacts()
output = ""
if feed:
for entry in feed.entry:
if entry.title and entry.title.text:
output += entry.title.text + "<br/>"
for email in entry.email:
if email.primary and email.primary == 'true':
output += ' %s<br/>' % (email.address)
self.response.headers['Content-Type'] = 'text/html'
self.response.write('''<h1>Contact Access via GData Client</h1>''' + output)

Resources