diff --git a/OPENAPI_DOC.yml b/OPENAPI_DOC.yml index ed2c5e2e..3413ffec 100644 --- a/OPENAPI_DOC.yml +++ b/OPENAPI_DOC.yml @@ -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: @@ -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: diff --git a/shard.lock b/shard.lock index 8e5a5a72..9af9b2fa 100644 --- a/shard.lock +++ b/shard.lock @@ -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 diff --git a/spec/controllers/calendars_spec.cr b/spec/controllers/calendars_spec.cr index c99bf7ec..1e7a8b9a 100644 --- a/spec/controllers/calendars_spec.cr +++ b/spec/controllers/calendars_spec.cr @@ -119,14 +119,13 @@ 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" @@ -134,71 +133,18 @@ describe Calendars do 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 diff --git a/spec/fixtures/calendars/o365/calendar_permissions_delegate_access.json b/spec/fixtures/calendars/o365/calendar_permissions_delegate_access.json deleted file mode 100644 index 96ec0e5d..00000000 --- a/spec/fixtures/calendars/o365/calendar_permissions_delegate_access.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('pradeep%40domain.com')/calendar/calendarPermissions", - "value": [ - { - "id": "RGVsZWdhdGVk", - "role": "delegateWithoutPrivateEventAccess", - "allowedRoles": [ - "freeBusyRead", - "limitedRead", - "read", - "write", - "delegateWithoutPrivateEventAccess" - ], - "isRemovable": true, - "isInsideOrganization": true, - "emailAddress": { - "name": "Developer", - "address": "dev@acaprojects.onmicrosoft.com" - } - } - ] -} \ No newline at end of file diff --git a/spec/fixtures/calendars/o365/calendar_permissions_no_access.json b/spec/fixtures/calendars/o365/calendar_permissions_no_access.json index 8311c598..21e2b7a4 100644 --- a/spec/fixtures/calendars/o365/calendar_permissions_no_access.json +++ b/spec/fixtures/calendars/o365/calendar_permissions_no_access.json @@ -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": "" - } - } - ] -} \ No newline at end of file + "error": { + "code": "ErrorItemNotFound", + "message": "The specified object was not found in the store." + } +} diff --git a/spec/fixtures/calendars/o365/calendar_permissions_read_only.json b/spec/fixtures/calendars/o365/calendar_permissions_read_only.json deleted file mode 100644 index 077e3911..00000000 --- a/spec/fixtures/calendars/o365/calendar_permissions_read_only.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('pradeep%40domain.com')/calendar/calendarPermissions", - "value": [ - { - "id": "UmVhZE9ubHk", - "role": "read", - "allowedRoles": [ - "freeBusyRead", - "limitedRead", - "read" - ], - "isRemovable": true, - "isInsideOrganization": true, - "emailAddress": { - "name": "Developer", - "address": "dev@acaprojects.onmicrosoft.com" - } - } - ] -} \ No newline at end of file diff --git a/spec/fixtures/calendars/o365/calendar_permissions_write_access.json b/spec/fixtures/calendars/o365/calendar_permissions_write_access.json index b055374f..c74cd4ff 100644 --- a/spec/fixtures/calendars/o365/calendar_permissions_write_access.json +++ b/spec/fixtures/calendars/o365/calendar_permissions_write_access.json @@ -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" - } - } - ] -} \ No newline at end of file + "@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" + } +} diff --git a/src/controllers/calendars.cr b/src/controllers/calendars.cr index 5b3c2d99..6692fca2 100644 --- a/src/controllers/calendars.cr +++ b/src/controllers/calendars.cr @@ -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