[Security] Fix PrivacyPrivilegeManager.RequestPermissions crash issue (#1650)
[platform/core/csapi/tizenfx.git] / src / Tizen.Security.PrivacyPrivilegeManager / Tizen.Security / PrivacyPrivilegeManager.cs
1 /*
2  * Copyright (c) 2017 - 2018 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Collections.Generic;
19 using System.Linq;
20 using System.Threading.Tasks;
21
22 namespace Tizen.Security
23 {
24     /// <summary>
25     /// The PrivacyPrivilegeManager provides the properties or methods to check and request a permission for privacy privilege.
26     /// </summary>
27     /// <since_tizen> 4 </since_tizen>
28     public static class PrivacyPrivilegeManager
29     {
30         private const string LogTag = "Tizen.Privilege";
31         private static Dictionary<int, TaskCompletionSource<RequestMultipleResponseEventArgs>> s_multipleRequestMap = new Dictionary<int, TaskCompletionSource<RequestMultipleResponseEventArgs>>();
32         private static int s_requestId = 0;
33         private static IDictionary<string, WeakReference<ResponseContext>> s_responseWeakMap = new Dictionary<string, WeakReference<ResponseContext>>();
34         private static Interop.PrivacyPrivilegeManager.RequestResponseCallback s_requestResponseCb =
35                        (Interop.PrivacyPrivilegeManager.CallCause cause, Interop.PrivacyPrivilegeManager.RequestResult result,
36                            string privilege, IntPtr userData) =>
37                         {
38                             try
39                             {
40                                 if (s_responseWeakMap.TryGetValue(privilege, out WeakReference<ResponseContext> weakRef))
41                                 {
42                                     if (weakRef.TryGetTarget(out ResponseContext context))
43                                     {
44                                         context.FireEvent((CallCause)cause, (RequestResult)result);
45                                     }
46                                     else
47                                     {
48                                         s_responseWeakMap.Remove(privilege);
49                                         Log.Error(LogTag, "No response context for: " + privilege);
50                                     }
51                                 }
52                                 else
53                                 {
54                                     Log.Error(LogTag, "No listener for: " + privilege);
55                                 }
56                             }
57                             catch (Exception e)
58                             {
59                                 Log.Error(LogTag, "Exception in callback : " + e.Message);
60                             }
61                             s_PrivilegesInProgress.Remove(privilege);
62                         };
63
64         private static IDictionary<string, ResponseContext> s_responseMap = new Dictionary<string, ResponseContext>();
65         private static HashSet<string> s_PrivilegesInProgress = new HashSet<string>();
66         private static Interop.PrivacyPrivilegeManager.RequestMultipleResponseCallback s_multipleCallback = MultipleRequestHandler;
67
68         private static string[] CheckPrivilegesArgument(IEnumerable<string> privileges, string methodName)
69         {
70             if (privileges == null || !privileges.Any())
71             {
72                 Log.Error(LogTag, "privileges for " + methodName + " are null or empty.");
73                 throw new ArgumentException("privileges for " + methodName + " are null or empty.");
74             }
75
76             foreach (var privilege in privileges)
77             {
78                 if (string.IsNullOrEmpty(privilege))
79                 {
80                     Log.Error(LogTag, " At least one privilege for " + methodName + " is null or empty.");
81                     throw new ArgumentException(" At least one privilege for " + methodName + " is null or empty.");
82                 }
83             }
84
85             return privileges as string[] ?? privileges.ToArray();
86         }
87
88         /// <summary>
89         /// Gets the status of a privacy privilege permission.
90         /// </summary>
91         /// <param name="privilege">The privacy privilege to be checked.</param>
92         /// <returns>The permission setting for a respective privilege.</returns>
93         /// <exception cref="ArgumentException">Thrown when an invalid parameter is passed.</exception>
94         /// <exception cref="OutOfMemoryException">Thrown when a memory error occurred.</exception>
95         /// <exception cref="System.IO.IOException">Thrown when the method failed due to an internal I/O error.</exception>
96         /// <example>
97         /// <code>
98         ///     CheckResult result = PrivacyPrivilegeManager.CheckPermission("http://tizen.org/privilege/account.read");
99         ///     switch (result)
100         ///     {
101         ///         case Allow:
102         ///             // Privilege can be used
103         ///             break;
104         ///         case Deny:
105         ///             // Privilege can't be used
106         ///             break;
107         ///         case Ask:
108         ///             // User permission request required
109         ///             PrivacyPrivilegeManager.RequestPermission("http://tizen.org/privilege/account.read");
110         ///             break;
111         ///     }
112         /// </code>
113         /// </example>
114         /// <since_tizen> 4 </since_tizen>
115         public static CheckResult CheckPermission(string privilege)
116         {
117             Interop.PrivacyPrivilegeManager.CheckResult result;
118             int ret = (int)Interop.PrivacyPrivilegeManager.CheckPermission(privilege, out result);
119             if (ret != (int)Interop.PrivacyPrivilegeManager.ErrorCode.None)
120             {
121                 Log.Error(LogTag, "Failed to check permission");
122                 throw PrivacyPrivilegeManagerErrorFactory.GetException(ret);
123             }
124             return (CheckResult)result;
125         }
126
127         /// <summary>
128         /// Gets the status of a privacy privileges permission.
129         /// </summary>
130         /// <param name="privileges">The privacy privileges to be checked.</param>
131         /// <returns>The permission setting for a respective privileges.</returns>
132         /// <exception cref="ArgumentException">Thrown when an invalid parameter is passed.</exception>
133         /// <exception cref="OutOfMemoryException">Thrown when a memory error occurred.</exception>
134         /// <exception cref="System.IO.IOException">Thrown when the method failed due to an internal I/O error.</exception>
135         /// <example>
136         /// <code>
137         ///     string[] privileges = new [] {"http://tizen.org/privilege/account.read",
138         ///                                   "http://tizen.org/privilege/alarm"};
139         ///     CheckResult[] results = PrivacyPrivilegeManager.CheckPermissions(privileges).ToArray();
140         ///     List&lt;string&gt; privilegesWithAskStatus = new List&lt;string&gt;();
141         ///     for (int iterator = 0; iterator &lt; results.Length; ++iterator)
142         ///     {
143         ///         switch (results[iterator])
144         ///         {
145         ///             case CheckResult.Allow:
146         ///                 // Privilege can be used
147         ///                 break;
148         ///             case CheckResult.Deny:
149         ///                 // Privilege can't be used
150         ///                 break;
151         ///             case CheckResult.Ask:
152         ///                 // User permission request required
153         ///                 privilegesWithAskStatus.Add(privileges[iterator]);
154         ///                 break;
155         ///         }
156         ///     }
157         ///     PrivacyPrivilegeManager.RequestPermissions(privilegesWithAskStatus);
158         /// </code>
159         /// </example>
160         /// <since_tizen> 6 </since_tizen>
161         public static IEnumerable<CheckResult> CheckPermissions(IEnumerable<string> privileges)
162         {
163             string[] privilegesArray = CheckPrivilegesArgument(privileges, "CheckPermissions");
164
165             Interop.PrivacyPrivilegeManager.CheckResult[] results = new Interop.PrivacyPrivilegeManager.CheckResult[privilegesArray.Length];
166             int ret = (int)Interop.PrivacyPrivilegeManager.CheckPermissions(privilegesArray, (uint)privilegesArray.Length, results);
167             if (ret != (int)Interop.PrivacyPrivilegeManager.ErrorCode.None)
168             {
169                 Log.Error(LogTag, "Failed to check permission");
170                 throw PrivacyPrivilegeManagerErrorFactory.GetException(ret);
171             }
172
173             CheckResult[] checkResults = new CheckResult[results.Length];
174             for (int iterator = 0; iterator < results.Length; ++iterator)
175             {
176                 checkResults[iterator] = (CheckResult)results[iterator];
177             }
178             return checkResults;
179         }
180
181
182         /// <summary>
183         /// Triggers the permission request for a user.
184         /// </summary>
185         /// <param name="privilege">The privacy privilege to be requested.</param>
186         /// <exception cref="ArgumentException">Thrown when an invalid parameter is passed.</exception>
187         /// <exception cref="OutOfMemoryException">Thrown when a memory error occurred.</exception>
188         /// <exception cref="System.IO.IOException">Thrown when the method failed due to an internal I/O error.</exception>
189         /// <example>
190         /// <code>
191         ///     CheckResult result = PrivacyPrivilegeManager.CheckPermission("http://tizen.org/privilege/account.read");
192         ///     switch (result)
193         ///     {
194         ///         case Allow:
195         ///             // Privilege can be used
196         ///             break;
197         ///         case Deny:
198         ///             // Privilege can't be used
199         ///             break;
200         ///         case Ask:
201         ///             // User permission request required
202         ///             PrivacyPrivilegeManager.RequestPermission("http://tizen.org/privilege/account.read");
203         ///             break;
204         ///     }
205         /// </code>
206         /// </example>
207         /// <since_tizen> 4 </since_tizen>
208         public static void RequestPermission(string privilege)
209         {
210             if (!s_PrivilegesInProgress.Add(privilege))
211             {
212                 Log.Error(LogTag, "Request for this privilege: " + privilege + " is already in progress.");
213                 throw new ArgumentException("Request for this privilege: " + privilege + " is already in progress.");
214             }
215
216             int ret = (int)Interop.PrivacyPrivilegeManager.RequestPermission(privilege, s_requestResponseCb, IntPtr.Zero);
217             if (ret != (int)Interop.PrivacyPrivilegeManager.ErrorCode.None)
218             {
219                 Log.Error(LogTag, "Failed to request permission");
220                 s_PrivilegesInProgress.Remove(privilege);
221                 throw PrivacyPrivilegeManagerErrorFactory.GetException(ret);
222             }
223         }
224
225         /// <summary>
226         /// Triggers the permissions request for a user.
227         /// </summary>
228         /// <param name="privileges">The privacy privileges to be requested.</param>
229         /// <exception cref="ArgumentException">Thrown when an invalid parameter is passed.</exception>
230         /// <exception cref="OutOfMemoryException">Thrown when a memory error occurred.</exception>
231         /// <exception cref="System.IO.IOException">Thrown when the method failed due to an internal I/O error.</exception>
232         /// <returns>Permission request Task</returns>
233         /// <example>
234         /// <code>
235         ///     string[] privileges = new [] {"http://tizen.org/privilege/account.read",
236         ///                                   "http://tizen.org/privilege/alarm"};
237         ///     CheckResult[] results = PrivacyPrivilegeManager.CheckPermissions(privileges).ToArray();
238         ///     List&lt;string&gt; privilegesWithAskStatus = new List&lt;string&gt;();
239         ///     for (int iterator = 0; iterator &lt; results.Length; ++iterator)
240         ///     {
241         ///         switch (results[iterator])
242         ///         {
243         ///             case CheckResult.Allow:
244         ///                 // Privilege can be used
245         ///                 break;
246         ///             case CheckResult.Deny:
247         ///                 // Privilege can't be used
248         ///                 break;
249         ///             case CheckResult.Ask:
250         ///                 // User permission request required
251         ///                 privilegesWithAskStatus.Add(privileges[iterator]);
252         ///                 break;
253         ///         }
254         ///     }
255         ///     IEnumerable&lt;PermissionRequestResponse&gt; responses = PrivacyPrivilegeManager.RequestPermissions(privilegesWithAskStatus).Result;
256         ///     //handle responses
257         /// </code>
258         /// </example>
259         /// <since_tizen> 6 </since_tizen>
260         public static Task<RequestMultipleResponseEventArgs> RequestPermissions(IEnumerable<string> privileges)
261         {
262             string[] privilegesArray = CheckPrivilegesArgument(privileges, "RequestPermissions");
263
264             for (int iterator = 0; iterator < privilegesArray.Length; ++iterator)
265             {
266                 if (!s_PrivilegesInProgress.Add(privilegesArray[iterator]))
267                 {
268                     Log.Error(LogTag, "Request for this privilege: " + privilegesArray[iterator] + " is already in progress.");
269
270                     for (int removeIterator = iterator - 1; removeIterator >= 0; --removeIterator)
271                     {
272                         s_PrivilegesInProgress.Remove(privilegesArray[removeIterator]);
273                     }
274                     Log.Error(LogTag, "Request for this privilege: " + privilegesArray[iterator] + " is already in progress.");
275                     throw new ArgumentException("Request for this privilege: " + privilegesArray[iterator] + " is already in progress.");
276                 }
277             }
278
279             Log.Info(LogTag, "Sending request for permissions: " + string.Join(" ", privilegesArray));
280
281             int requestId = 0;
282             lock (s_multipleRequestMap)
283             {
284                 requestId = s_requestId++;
285             }
286             TaskCompletionSource<RequestMultipleResponseEventArgs> permissionResponsesTask = new TaskCompletionSource<RequestMultipleResponseEventArgs>();
287             s_multipleRequestMap[requestId] = permissionResponsesTask;
288             int ret = (int)Interop.PrivacyPrivilegeManager.RequestPermissions(privilegesArray, (uint)privilegesArray.Length, s_multipleCallback, (IntPtr)requestId);
289
290             if (ret != (int)Interop.PrivacyPrivilegeManager.ErrorCode.None)
291             {
292                 Log.Error(LogTag, "Failed to request permissions.");
293                 foreach (string privilege in privileges)
294                 {
295                     s_PrivilegesInProgress.Remove(privilege);
296                 }
297                 s_multipleRequestMap.Remove(requestId);
298                 throw PrivacyPrivilegeManagerErrorFactory.GetException(ret);
299             }
300             else
301             {
302                 Log.Info(LogTag, "Requesting permissions successfull.");
303                 return permissionResponsesTask.Task;
304             }
305         }
306
307         /// <summary>
308         /// Gets the response context for a given privilege.
309         /// </summary>
310         /// <seealso cref="ResponseContext"/>
311         /// <param name="privilege">The privilege.</param>
312         /// <returns>The response context of a respective privilege.</returns>
313         /// <exception cref="ArgumentException">Thrown if the key is an invalid parameter.</exception>
314         /// <example>
315         /// <code>
316         /// private static void PPM_RequestResponse(object sender, RequestResponseEventArgs e)
317         /// {
318         ///     if (e.cause == CallCause.Answer)
319         ///     {
320         ///        switch(e.result)
321         ///
322         ///        {
323         ///
324         ///         case RequestResult.AllowForever:
325         ///             Console.WriteLine("User allowed usage of privilege {0} definitely", e.privilege);
326         ///             break;
327         ///         case RequestResult.DenyForever:
328         ///             Console.WriteLine("User denied usage of privilege {0} definitely", e.privilege);
329         ///             break;
330         ///         case RequestResult.DenyOnce:
331         ///             Console.WriteLine("User denied usage of privilege {0} this time", e.privilege);
332         ///             break;
333         ///         };
334         ///     }
335         ///     else
336         ///     {
337         ///         Console.WriteLine("Error occured during requesting permission for {0}", e.privilege);
338         ///     }
339         ///}
340         ///
341         ///     PrivacyPrivilegeManager.ResponseContext context = null;
342         ///     PrivacyPrivilegeManager.GetResponseContext("http://tizen.org/privilege/account.read").TryGetTarget(out context);
343         ///     if(context != null)
344         ///     {
345         ///         context.ResponseFetched += PPM_RequestResponse;
346         ///     }
347         ///
348         ///     PrivacyPrivilegeManager.RequestPermission("http://tizen.org/privilege/account.read");
349         ///
350         ///     PrivacyPrivilegeManager.GetResponseContext("http://tizen.org/privilege/account.read").TryGetTarget(out context);
351         ///     if(context != null)
352         ///     {
353         ///         context.ResponseFetched -= PPM_RequestResponse;
354         ///     }
355         /// </code>
356         /// </example>
357         /// <since_tizen> 4 </since_tizen>
358         public static WeakReference<ResponseContext> GetResponseContext(string privilege)
359         {
360             if (!(s_responseWeakMap.TryGetValue(privilege, out WeakReference<ResponseContext> weakRef) && weakRef.TryGetTarget(out ResponseContext context)))
361             {
362                 context = new ResponseContext(privilege);
363                 s_responseWeakMap[privilege] = new WeakReference<ResponseContext>(context);
364             }
365             return s_responseWeakMap[privilege];
366         }
367
368         private static void MultipleRequestHandler(Interop.PrivacyPrivilegeManager.CallCause cause, Interop.PrivacyPrivilegeManager.RequestResult[] results,
369             string[] requestedPrivileges, uint privilegesCount, IntPtr userData)
370         {
371             int requestId = (int)userData;
372             if (!s_multipleRequestMap.ContainsKey(requestId))
373             {
374                 return;
375             }
376
377             var tcs = s_multipleRequestMap[requestId];
378             RequestMultipleResponseEventArgs requestResponse = new RequestMultipleResponseEventArgs();
379             PermissionRequestResponse[] permissionResponses = new PermissionRequestResponse[privilegesCount];
380
381             for (int iterator = 0; iterator < privilegesCount; ++iterator)
382             {
383                 permissionResponses[iterator] = new PermissionRequestResponse
384                 {
385                     Privilege = requestedPrivileges[iterator],
386                     Result = (RequestResult)results[iterator]
387                 };
388             }
389             requestResponse.Cause = (CallCause)cause;
390             requestResponse.Responses = permissionResponses;
391
392             foreach (string privilege in requestedPrivileges)
393             {
394                 s_PrivilegesInProgress.Remove(privilege);
395             }
396             tcs.SetResult(requestResponse);
397             s_multipleRequestMap.Remove(requestId);
398         }
399
400         /// <summary>
401         /// This class manages event handlers of the privilege permission requests.
402         /// This class enables having event handlers for an individual privilege.
403         /// </summary>
404         /// <since_tizen> 4 </since_tizen>
405         public class ResponseContext
406         {
407             private string _privilege;
408
409             internal ResponseContext(string privilege)
410             {
411                 _privilege = privilege;
412             }
413             /// <summary>
414             /// Occurs when the response for a permission request is fetched.
415             /// </summary>
416             /// <exception cref="System.InvalidOperationException">Thrown when the bundle instance has been disposed.</exception>
417             /// <since_tizen> 4 </since_tizen>
418             public event EventHandler<RequestResponseEventArgs> ResponseFetched
419             {
420                 add
421                 {
422                     if (_ResponseFetched == null)
423                     {
424                         if (!s_responseMap.ContainsKey(_privilege))
425                         {
426                             s_responseMap[_privilege] = this;
427                         }
428                     }
429                     _ResponseFetched += value;
430                 }
431
432                 remove
433                 {
434                     _ResponseFetched -= value;
435                     if (_ResponseFetched == null)
436                     {
437                         if (s_responseMap.ContainsKey(_privilege))
438                         {
439                             s_responseMap.Remove(_privilege);
440                         }
441                     }
442                 }
443             }
444
445             private event EventHandler<RequestResponseEventArgs> _ResponseFetched;
446
447             internal void FireEvent(CallCause _cause, RequestResult _result)
448             {
449                 _ResponseFetched?.Invoke(this, new RequestResponseEventArgs { cause = _cause, result = _result, privilege = _privilege });
450             }
451         }
452     }
453
454     internal static class PrivacyPrivilegeManagerErrorFactory
455     {
456         static internal Exception GetException(int error)
457         {
458             Interop.PrivacyPrivilegeManager.ErrorCode errCode = (Interop.PrivacyPrivilegeManager.ErrorCode)error;
459             switch (errCode)
460             {
461                 case Interop.PrivacyPrivilegeManager.ErrorCode.InvalidParameter:
462                     return new ArgumentException("Invalid parameter");
463                 case Interop.PrivacyPrivilegeManager.ErrorCode.IoError:
464                     return new System.IO.IOException("I/O Error");
465                 case Interop.PrivacyPrivilegeManager.ErrorCode.OutOfMemory:
466                     return new OutOfMemoryException("Out of memory");
467                 case Interop.PrivacyPrivilegeManager.ErrorCode.Unknown:
468                 default:
469                     return new ArgumentException("Unknown error");
470             }
471         }
472     }
473 }