@@ -83,11 +83,12 @@ public GraphApiService(ILogger<GraphApiService> logger, CommandExecutor executor
8383 if ( ! accountCheck . Success )
8484 {
8585 _logger . LogInformation ( "Azure CLI not authenticated. Initiating login..." ) ;
86+ _logger . LogInformation ( "A browser window will open for authentication. Please check your taskbar or browser if you don't see it." ) ;
8687 var loginResult = await _executor . ExecuteAsync (
87- "az" ,
88- $ "login --tenant { tenantId } ",
88+ "az" ,
89+ $ "login --tenant { tenantId } ",
8990 cancellationToken : ct ) ;
90-
91+
9192 if ( ! loginResult . Success )
9293 {
9394 _logger . LogError ( "Azure CLI login failed" ) ;
@@ -491,19 +492,16 @@ public async Task<bool> CreateOrUpdateOauth2PermissionGrantAsync(
491492 }
492493
493494 /// <summary>
494- /// Ensures the current user is an owner of an application (idempotent operation).
495- /// First checks if the user is already an owner, and only adds if not present.
496- /// This ensures the creator has ownership permissions for setting callback URLs and bot IDs via the Developer Portal.
497- /// Requires Application.ReadWrite.All or Directory.ReadWrite.All permissions.
498- /// See: https://learn.microsoft.com/en-us/graph/api/application-post-owners?view=graph-rest-beta
495+ /// Checks if a user is an owner of an application (read-only validation).
496+ /// Does not attempt to add the user as owner, only verifies ownership.
499497 /// </summary>
500498 /// <param name="tenantId">The tenant ID</param>
501499 /// <param name="applicationObjectId">The application object ID (not the client/app ID)</param>
502- /// <param name="userObjectId">The user's object ID to add as owner . If null, uses the current authenticated user.</param>
500+ /// <param name="userObjectId">The user's object ID to check . If null, uses the current authenticated user.</param>
503501 /// <param name="ct">Cancellation token</param>
504502 /// <param name="scopes">OAuth2 scopes for elevated permissions (e.g., Application.ReadWrite.All, Directory.ReadWrite.All)</param>
505- /// <returns>True if the user is an owner (either already was or was successfully added) , false otherwise</returns>
506- public virtual async Task < bool > AddApplicationOwnerAsync (
503+ /// <returns>True if the user is an owner, false otherwise</returns>
504+ public virtual async Task < bool > IsApplicationOwnerAsync (
507505 string tenantId ,
508506 string applicationObjectId ,
509507 string ? userObjectId = null ,
@@ -517,7 +515,7 @@ public virtual async Task<bool> AddApplicationOwnerAsync(
517515 {
518516 if ( ! await EnsureGraphHeadersAsync ( tenantId , ct , scopes ) )
519517 {
520- _logger . LogWarning ( "Could not acquire Graph token to add application owner" ) ;
518+ _logger . LogWarning ( "Could not acquire Graph token to check application owner" ) ;
521519 return false ;
522520 }
523521
@@ -542,105 +540,32 @@ public virtual async Task<bool> AddApplicationOwnerAsync(
542540 }
543541
544542 userObjectId = idElement . GetString ( ) ;
545- _logger . LogDebug ( "Retrieved current user's object ID: {UserId}" , userObjectId ) ;
546543 }
547544
548545 if ( string . IsNullOrWhiteSpace ( userObjectId ) )
549546 {
550- _logger . LogWarning ( "User object ID is empty, cannot add as owner" ) ;
547+ _logger . LogWarning ( "User object ID is empty, cannot check owner" ) ;
551548 return false ;
552549 }
553550
554- // Check if user is already an owner (idempotency check)
555- _logger . LogDebug ( "Checking if user {UserId} is already an owner of application {AppObjectId}" , userObjectId , applicationObjectId ) ;
551+ // Check if user is an owner
552+ _logger . LogDebug ( "Checking if user {UserId} is an owner of application {AppObjectId}" , userObjectId , applicationObjectId ) ;
556553
557554 var ownersDoc = await GraphGetAsync ( tenantId , $ "/v1.0/applications/{ applicationObjectId } /owners?$select=id", ct , scopes ) ;
558555 if ( ownersDoc != null && ownersDoc . RootElement . TryGetProperty ( "value" , out var ownersArray ) )
559556 {
560- var isAlreadyOwner = ownersArray . EnumerateArray ( )
557+ var isOwner = ownersArray . EnumerateArray ( )
561558 . Where ( owner => owner . TryGetProperty ( "id" , out var ownerId ) )
562559 . Any ( owner => string . Equals ( owner . GetProperty ( "id" ) . GetString ( ) , userObjectId , StringComparison . OrdinalIgnoreCase ) ) ;
563560
564- if ( isAlreadyOwner )
565- {
566- _logger . LogDebug ( "User is already an owner of the application" ) ;
567- return true ;
568- }
569- }
570-
571- // User is not an owner, add them
572- // https://learn.microsoft.com/en-us/graph/api/application-post-owners?view=graph-rest-beta
573- _logger . LogDebug ( "Adding user {UserId} as owner to application {AppObjectId}" , userObjectId , applicationObjectId ) ;
574-
575- var payload = new JsonObject
576- {
577- [ "@odata.id" ] = $ "{ GraphApiConstants . BaseUrl } /{ GraphApiConstants . Versions . Beta } /directoryObjects/{ userObjectId } "
578- } ;
579-
580- // Use beta endpoint as recommended in the documentation
581- var relativePath = $ "/beta/applications/{ applicationObjectId } /owners/$ref";
582-
583- if ( ! await EnsureGraphHeadersAsync ( tenantId , ct , scopes ) )
584- {
585- _logger . LogWarning ( "Could not authenticate to Graph API to add application owner" ) ;
586- return false ;
587- }
588-
589- var url = $ "{ GraphApiConstants . BaseUrl } { relativePath } ";
590- using var content = new StringContent (
591- payload . ToJsonString ( ) ,
592- Encoding . UTF8 ,
593- "application/json" ) ;
594-
595- using var response = await _httpClient . PostAsync ( url , content , ct ) ;
596-
597- if ( response . IsSuccessStatusCode )
598- {
599- _logger . LogInformation ( "Successfully added user as owner to application" ) ;
600- return true ;
601- }
602-
603- var errorBody = await response . Content . ReadAsStringAsync ( ct ) ;
604-
605- // Check if the user is already an owner (409 Conflict or specific error message)
606- // This handles race conditions where the user was added between our check and the POST
607- if ( ( int ) response . StatusCode == 409 ||
608- errorBody . Contains ( "already exist" , StringComparison . OrdinalIgnoreCase ) ||
609- errorBody . Contains ( "One or more added object references already exist" , StringComparison . OrdinalIgnoreCase ) )
610- {
611- _logger . LogDebug ( "User is already an owner of the application (detected during add)" ) ;
612- return true ;
613- }
614-
615- // Log specific error guidance based on status code
616- _logger . LogWarning ( "Failed to add user as owner to application. Status: {Status}, URL: {Url}" ,
617- response . StatusCode , url ) ;
618-
619- if ( response . StatusCode == HttpStatusCode . Forbidden )
620- {
621- _logger . LogWarning ( "Access denied. Ensure the authenticated user has Application.ReadWrite.All or Directory.ReadWrite.All permissions" ) ;
622- _logger . LogWarning ( "To manually add yourself as an owner, make this Graph API call:" ) ;
623- _logger . LogWarning ( " POST {Url}" , url ) ;
624- _logger . LogWarning ( " Content-Type: application/json" ) ;
625- _logger . LogWarning ( " Body: {{\" @odata.id\" : \" {ODataId}\" }}" , $ "{ GraphApiConstants . BaseUrl } /{ GraphApiConstants . Versions . Beta } /directoryObjects/{ userObjectId } ") ;
626- }
627- else if ( response . StatusCode == HttpStatusCode . NotFound )
628- {
629- _logger . LogWarning ( "Application or user not found. Verify ObjectId: {AppObjectId}, UserId: {UserId}" ,
630- applicationObjectId , userObjectId ) ;
631- }
632- else if ( response . StatusCode == HttpStatusCode . BadRequest )
633- {
634- _logger . LogWarning ( "Bad request. Verify the payload format and user object ID" ) ;
635- _logger . LogWarning ( "Attempted payload: {{\" @odata.id\" : \" {ODataId}\" }}" , $ "{ GraphApiConstants . BaseUrl } /{ GraphApiConstants . Versions . Beta } /directoryObjects/{ userObjectId } ") ;
561+ return isOwner ;
636562 }
637563
638- _logger . LogDebug ( "Graph API error response: {Error}" , errorBody ) ;
639564 return false ;
640565 }
641566 catch ( Exception ex )
642567 {
643- _logger . LogWarning ( ex , "Error adding user as owner to application: {Message}" , ex . Message ) ;
568+ _logger . LogWarning ( ex , "Error checking if user is owner of application: {Message}" , ex . Message ) ;
644569 return false ;
645570 }
646571 }
0 commit comments