Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions OPENAPI_DOC.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3886,7 +3886,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/Calendars__PermissionCheck'
$ref: '#/components/schemas/NamedTuple_can_edit__Bool_'
429:
description: Too Many Requests
content:
Expand Down Expand Up @@ -12553,16 +12553,13 @@ components:
required:
- summary
- primary
Calendars__PermissionCheck:
NamedTuple_can_edit__Bool_:
type: object
properties:
has_access:
can_edit:
type: boolean
role:
type: string
required:
- has_access
- role
- can_edit
Calendars__Availability:
type: object
properties:
Expand Down
2 changes: 1 addition & 1 deletion shard.lock
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ shards:

placeos-models:
git: https://github.com/placeos/models.git
version: 9.89.0
version: 9.89.1

promise:
git: https://github.com/spider-gazelle/promise.git
Expand Down
66 changes: 6 additions & 60 deletions spec/controllers/calendars_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -119,86 +119,32 @@ describe Calendars do
response.status_code.should eq(200)

body = JSON.parse(response.body)
body["has_access"].should eq(true)
body["role"].should eq("owner")
body["can_edit"].should eq(true)
end

it "should return write access when user has write permission" do
WebMock.stub(:post, "https://login.microsoftonline.com/bb89674a-238b-4b7d-91ec-6bebad83553a/oauth2/v2.0/token")
.to_return(body: File.read("./spec/fixtures/tokens/o365_token.json"))
WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/pradeep%40domain.com/calendar/calendarPermissions")
WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/pradeep%40domain.com/calendar")
.to_return(body: File.read("./spec/fixtures/calendars/o365/calendar_permissions_write_access.json"))

route = "#{CALENDARS_BASE}/pradeep@domain.com/permission"
response = client.get(route, headers: headers)
response.status_code.should eq(200)

body = JSON.parse(response.body)
body["has_access"].should eq(true)
body["role"].should eq("write")
end

it "should return delegate access when user has delegate permission" do
WebMock.stub(:post, "https://login.microsoftonline.com/bb89674a-238b-4b7d-91ec-6bebad83553a/oauth2/v2.0/token")
.to_return(body: File.read("./spec/fixtures/tokens/o365_token.json"))
WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/pradeep%40domain.com/calendar/calendarPermissions")
.to_return(body: File.read("./spec/fixtures/calendars/o365/calendar_permissions_delegate_access.json"))

route = "#{CALENDARS_BASE}/pradeep@domain.com/permission"
response = client.get(route, headers: headers)
response.status_code.should eq(200)

body = JSON.parse(response.body)
body["has_access"].should eq(true)
body["role"].should eq("delegateWithoutPrivateEventAccess")
end

it "should return no access when user has only read permission" do
WebMock.stub(:post, "https://login.microsoftonline.com/bb89674a-238b-4b7d-91ec-6bebad83553a/oauth2/v2.0/token")
.to_return(body: File.read("./spec/fixtures/tokens/o365_token.json"))
WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/pradeep%40domain.com/calendar/calendarPermissions")
.to_return(body: File.read("./spec/fixtures/calendars/o365/calendar_permissions_read_only.json"))

route = "#{CALENDARS_BASE}/pradeep@domain.com/permission"
response = client.get(route, headers: headers)
response.status_code.should eq(200)

body = JSON.parse(response.body)
body["has_access"].should eq(false)
body["role"].should eq("read")
body["can_edit"].should eq(true)
end

it "should return no access when user is not in permissions list" do
WebMock.stub(:post, "https://login.microsoftonline.com/bb89674a-238b-4b7d-91ec-6bebad83553a/oauth2/v2.0/token")
.to_return(body: File.read("./spec/fixtures/tokens/o365_token.json"))
WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/pradeep%40domain.com/calendar/calendarPermissions")
.to_return(body: File.read("./spec/fixtures/calendars/o365/calendar_permissions_no_access.json"))

route = "#{CALENDARS_BASE}/pradeep@domain.com/permission"
response = client.get(route, headers: headers)
response.status_code.should eq(200)

body = JSON.parse(response.body)
body["has_access"].should eq(false)
body["role"].should eq("none")
end

it "should handle errors gracefully and return error role" do
WebMock.stub(:post, "https://login.microsoftonline.com/bb89674a-238b-4b7d-91ec-6bebad83553a/oauth2/v2.0/token")
.to_return(body: File.read("./spec/fixtures/tokens/o365_token.json"))
# Stub with both encoded and unencoded versions to ensure match
WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/pradeep%40domain.com/calendar/calendarPermissions")
.to_return(status: 500, body: "Internal Server Error")
WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/pradeep@domain.com/calendar/calendarPermissions")
.to_return(status: 500, body: "Internal Server Error")
WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/pradeep%40domain.com/calendar")
.to_return(status: 404, body: File.read("./spec/fixtures/calendars/o365/calendar_permissions_no_access.json"))

route = "#{CALENDARS_BASE}/pradeep@domain.com/permission"
response = client.get(route, headers: headers)
response.status_code.should eq(200)

body = JSON.parse(response.body)
body["has_access"].should eq(false)
body["role"].should eq("error")
response.status_code.should eq(404)
end
end
end
Expand Down

This file was deleted.

25 changes: 5 additions & 20 deletions spec/fixtures/calendars/o365/calendar_permissions_no_access.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,6 @@
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('pradeep%40domain.com')/calendar/calendarPermissions",
"value": [
{
"id": "T3JnYW5pemF0aW9u",
"role": "freeBusyRead",
"allowedRoles": [
"freeBusyRead",
"limitedRead",
"read",
"write"
],
"isRemovable": false,
"isInsideOrganization": true,
"emailAddress": {
"name": "My Organization",
"address": ""
}
}
]
}
"error": {
"code": "ErrorItemNotFound",
"message": "The specified object was not found in the store."
}
}
20 changes: 0 additions & 20 deletions spec/fixtures/calendars/o365/calendar_permissions_read_only.json

This file was deleted.

40 changes: 20 additions & 20 deletions spec/fixtures/calendars/o365/calendar_permissions_write_access.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('pradeep%40domain.com')/calendar/calendarPermissions",
"value": [
{
"id": "RGVsZWdhdGVk",
"role": "write",
"allowedRoles": [
"freeBusyRead",
"limitedRead",
"read",
"write"
],
"isRemovable": true,
"isInsideOrganization": true,
"emailAddress": {
"name": "Developer",
"address": "dev@acaprojects.onmicrosoft.com"
}
}
]
}
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('PradeepG%400cbfs.onmicrosoft.com')/calendar/$entity",
"id": "AAMkADU1YTY0ZWJmLTRmNTItNGY3Yi1hZTQ2LWViMGRjODJkMzQxMQAuAAAAAABNcEmr5z91TbEyHQfold5aAQBOyWVhrznkS70e6DEHcQuHAAAAAAENAAA=",
"name": "Calendar",
"color": "auto",
"hexColor": "",
"groupClassId": "00000000-0000-0000-0000-000000000000",
"isDefaultCalendar": true,
"changeKey": "TsllYa855Eu9HugxB3ELhwAC51lDZQ==",
"canShare": false,
"canViewPrivateItems": false,
"canEdit": true,
"allowedOnlineMeetingProviders": ["teamsForBusiness"],
"defaultOnlineMeetingProvider": "teamsForBusiness",
"isTallyingResponses": true,
"isRemovable": false,
"owner": {
"name": "Pradeep Gupta",
"address": "PradeepG@0cbfs.onmicrosoft.com"
}
}
26 changes: 6 additions & 20 deletions src/controllers/calendars.cr
Original file line number Diff line number Diff line change
Expand Up @@ -46,45 +46,31 @@ class Calendars < Application
client.list_calendars(user.email)
end

record PermissionCheck, has_access : Bool, role : String do
include JSON::Serializable
end

# Check if current user has write access to specified user's calendar
@[AC::Route::GET("/:user_email/permission")]
def check_permission(
@[AC::Param::Info(description: "email or UPN of calendar owner", example: "foo@domain.com")]
user_email : String,
) : PermissionCheck
) : NamedTuple(can_edit: Bool)
current_user_email = user.email.downcase
target_email = user_email.downcase

# User always has permission to their own calendar
if current_user_email == target_email
return PermissionCheck.new(has_access: true, role: "owner")
return {can_edit: true}
end

# Get Office365 client and call calendarPermissions API
if client.client_id == :office365
o365_client = client.calendar.as(PlaceCalendar::Office365).client
permissions_query = o365_client.list_calendar_permissions(target_email)

# Find current user in permissions list
user_permission = permissions_query.value.find do |perm|
perm.email_address.address.try(&.downcase) == current_user_email
end

if user_permission && user_permission.can_edit?
PermissionCheck.new(has_access: true, role: user_permission.role)
else
PermissionCheck.new(has_access: false, role: user_permission.try(&.role) || "none")
end
cal_res = o365_client.get_calendar(mailbox: target_email)
{can_edit: cal_res.can_edit? || false}
else
PermissionCheck.new(has_access: false, role: "unsupported")
{can_edit: false}
end
rescue ex
Log.warn(exception: ex) { "failed to check calendar permission for #{target_email}" }
PermissionCheck.new(has_access: false, role: "error")
raise Error::NotFound.new(ex.message || {error: "Not Found"}.to_json)
end

# checks for availability of matched calendars, returns a list of calendars with availability
Expand Down
Loading