UWP aplikace mají nabízet uživateli vyšší bezpečnost a explicitní kontrolu nad tím, na co mohou na jeho počítači přistupovat. Ruku v ruce s tím přichází i možnost ručně povolit či zakázat kterékoliv z oprávnění v systémových nastaveních. Jak může pak vývojář aplikace zajistit, že má opravdu právo provádět určitou akci, když jej může uživatel kdykoliv upřít? To si ukážeme v tomto článku.
Co se může stát?
Probém si ukážeme na přímočarém příkladu. Vaše aplikace deklaruje Pictures library capability v Package.appxmanifest
.

V kódu můžeme obvyklým způsobem přistoupit k dané knihovně a očekáváme, že toto volání proběhne bez problémů:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var library = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures); |
A je tomu skutečně tak až do chvíle, kdy uživatel právo explicitně odebere v Nastavení -> Aplikace -> [Název aplikace] -> Pokročilá nastavení -> Práva aplikace:

Když aplikaci znovu spustíme a pokusíme se přístoupit ke knihovně obráyků, skončíme s ošklivou chybou UnauthorizedAccessException
:

Řešení
Přístup ke knihovně uzavřeme do pomocné metody, která bude obsahovat logiku, která zajistí, že situaci zvládneme bez pádu aplikace.
Prvním klíčovým krokem je odchycení chyby UnauthorizedAccessException
. Pomůže nám jednoduchý try...catch
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private async Task<StorageLibrary> TryAccessLibraryAsync(KnownLibraryId library) | |
{ | |
try | |
{ | |
return await StorageLibrary.GetLibraryAsync(library); | |
} | |
catch (UnauthorizedAccessException) | |
{ | |
… | |
} | |
} |
Jakmile jsme uvnitř catch
, víme že jsme nedostali přístup ke knihovně a musíme tedy uživatele znovu požádat o povolení přístupu. Uděláme to prostřednictvím MessageDialog
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//explain the issue | |
MessageDialog requestPermissionDialog = | |
new MessageDialog($"The app needs to access the {library}. " + | |
"Press OK to open system settings and give this app permission. " + | |
"If the app closes, reopen it afterwards. " + | |
"If you Cancel, the app will have limited functionality only."); | |
//setup dialog commands | |
var okCommand = new UICommand("OK"); | |
requestPermissionDialog.Commands.Add(okCommand); | |
var cancelCommand = new UICommand("Cancel"); | |
requestPermissionDialog.Commands.Add(cancelCommand); | |
requestPermissionDialog.DefaultCommandIndex = 0; | |
requestPermissionDialog.CancelCommandIndex = 1; | |
//display to user | |
var requestPermissionResult = await requestPermissionDialog.ShowAsync(); |
Pokud se uživatel rozhodne žádost zrušit tlačítkem Cancel
, vracíme jednoduše null
. V závislosti na tom jak důležité je povolení přístupu pro funkčnost naší aplikace pak můžeme buď dané funkce omezit, nebo aplikaci úplně zavřít.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (requestPermissionResult == cancelCommand) | |
{ | |
//user chose to Cancel, app will not have permission | |
return null; | |
} |
Pokud uživatel klikne na OK
, otevřeme pro něj odpovídající stránku s detaily naší aplikace v Nastaveních pomocí speciální URI ms-settings:appsfeatures-app
(viz Docs).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//open app settings to allow users to give us permission | |
await Launcher.LaunchUriAsync(new Uri("ms-settings:appsfeatures-app")); | |
//confirmation dialog to retry | |
var confirmationDialog = new MessageDialog($"Please give this app the {library} permission " + | |
"in the Settings app which has now opened."); | |
confirmationDialog.Commands.Add(okCommand); | |
await confirmationDialog.ShowAsync(); |
Přestože uvedené příklady ilustrovaly na oprávnění přístupu k souborovým knihovnám uživatele, lze kód analogicky použít i pro ostatní oprávnění které aplikace deklaruje.
Varování – nezapomeňte na data uživatele
Toto řešení má jediný háček – jakákoliv změna práv aplikace nemůže být provedena za jejího běhu. Ve chvíli kdy uživatel práva jakkoliv změní, systém aplikací ukončí. Z tohoto důvodu MessageDialog
uživatele informuje, aby aplikaci následně znovu otevřel. Je však pro nás důležité, abychom vždy bezpečně uložili všechna rozpracovaná data uživatele před přístupem k API které vyžaduje oprávnění. Tak zajistíme, že budeme vždy schopni data obnovit při novém spuštění a nenarušíme tak uživatelské flow.
Alternativně můžeme všechna oprávnění kontrolovat při startu aplikace. Výhodou tohoto řešní je fakt, že uživatel tu chvíli nemůže mít žádná rozpracovaná data a vypnutí aplikace je tak naprosto bezpečné.
Zdrojový kód
Kompletní zdrojový kód našeho příkladu vypadá následovně:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private async Task<StorageLibrary> TryAccessLibraryAsync(KnownLibraryId library) | |
{ | |
try | |
{ | |
return await StorageLibrary.GetLibraryAsync(library); | |
} | |
catch (UnauthorizedAccessException) | |
{ | |
//explain the issue | |
MessageDialog requestPermissionDialog = | |
new MessageDialog( | |
$"The app needs to access the {library}. " + | |
"Press OK to open system settings and give this app permission. " + | |
"If the app closes, reopen it afterwards. " + | |
"If you Cancel, the app will have limited functionality only."); | |
var okCommand = new UICommand("OK"); | |
requestPermissionDialog.Commands.Add(okCommand); | |
var cancelCommand = new UICommand("Cancel"); | |
requestPermissionDialog.Commands.Add(cancelCommand); | |
requestPermissionDialog.DefaultCommandIndex = 0; | |
requestPermissionDialog.CancelCommandIndex = 1; | |
var requestPermissionResult = await requestPermissionDialog.ShowAsync(); | |
if (requestPermissionResult == cancelCommand) | |
{ | |
//user chose to Cancel, app will not have permission | |
return null; | |
} | |
//open app settings to allow users to give us permission | |
await Launcher.LaunchUriAsync(new Uri("ms-settings:appsfeatures-app")); | |
//confirmation dialog to retry | |
var confirmationDialog = new MessageDialog( | |
$"Please give this app the {library} permission " + | |
"in the Settings app which has now opened."); | |
confirmationDialog.Commands.Add(okCommand); | |
await confirmationDialog.ShowAsync(); | |
} | |
return null; | |
} |
Kompletní ukázkové řešení můžete najít na mém GitHubu.
Shrnutí
Zjistili jsme jak můžeme bezpečně zvládnout situaci, kdy uživatel aplikaci zabrání využívat oprávnění, které vyžaduje pro plnou funkčnost. Tím nabídneme skvělou uživatelskou přívětivost a můžeme navíc přesně vysvětlit proč dané API potřebujeme.