3c4d08646f63ae5951f6cb58d12b7df010b80b70
[framework/security/security-server.git] / ace_client / src / ace_client.cpp
1 /*
2  * Copyright (c) 2011 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  * @file        ace_client.cpp
18  * @author      Tomasz Swierczek (t.swierczek@samsung.com)
19  * @version     1.0
20  * @brief       This file contains implementation of AceThinClient class
21  */
22
23 #include <memory>
24 #include <set>
25 #include <map>
26
27 #include <dpl/optional.h>
28 #include <dpl/string.h>
29 #include <dpl/optional_typedefs.h>
30 #include <dpl/log/log.h>
31 #include <dpl/singleton_safe_impl.h>
32 #include <ace-dao-ro/PromptModel.h>
33
34 #include <ace_popup_handler.h>
35
36 #include "ace_server_api.h"
37 #include "popup_response_server_api.h"
38 #include "ace-client/ace_client.h"
39 #include "ace-client/ace_client_helper.h"
40 #include <attribute_facade.h>
41 #include <ace/Request.h>
42
43 // ACE tests need to use mock implementations
44 #ifdef ACE_CLIENT_TESTS
45
46 #include "AceDAOReadOnly_mock.h"
47 #include "communication_client_mock.h"
48 #include "PolicyInformationPoint_mock.h"
49
50 #else
51
52 #include <ace-dao-ro/AceDAOReadOnly.h>
53 #include "SecurityCommunicationClient.h"
54 #include <ace/PolicyInformationPoint.h>
55
56 #endif // ACE_CLIENT_TESTS
57
58 IMPLEMENT_SAFE_SINGLETON(AceClient::AceThinClient)
59
60 ace_popup_handler_func_t popup_func = NULL;
61
62 namespace AceClient {
63
64 namespace {
65 // These devcaps actually are not requested in config file, so should be treaded
66 // as if were requested (access tags/WARP will block request if desired)
67 const std::string DEVCAP_EXTERNAL_NETWORK_ACCESS = "externalNetworkAccess";
68 const std::string DEVCAP_XML_HTTP_REQUEST = "XMLHttpRequest";
69 } // anonymous
70
71
72 std::string AceFunctionParam::aceFunctionParamToken = "param:function";
73
74 // popup cache result
75
76 enum class AceCachedPromptResult {
77     PERMIT,
78     DENY,
79     ASK_POPUP
80 };
81
82 // AceThinClient implementation singleton
83 class AceThinClientImpl {
84   public:
85     bool checkFunctionCall(const AceRequest& ace_request);
86     AcePreference getWidgetResourcePreference(
87             const AceResource& resource,
88             const AceWidgetHandle& handle) const;
89     AceResourcesPreferences* getGlobalResourcesPreferences() const;
90     bool isInitialized() const;
91
92     AceThinClientImpl();
93     ~AceThinClientImpl();
94
95   protected:
96     bool containsNetworkDevCap(const AceRequest &ace_request);
97     bool checkFeatureList(const AceRequest& ace_request);
98   private:
99     WebRuntimeImpl* m_wrt;
100     ResourceInformationImpl* m_res;
101     OperationSystemImpl* m_sys;
102     WrtSecurity::Communication::Client *m_communicationClient, *m_popupValidationClient;
103
104     AceSubject getSubjectForHandle(AceWidgetHandle handle) const;
105     AceCachedPromptResult getCachedPromptResult(
106             WidgetHandle widgetHandle,
107             int ruleId,
108             const AceSessionId& sessionId) const;
109     bool askUser(PolicyEffect popupType,
110                 const AceRequest& ace_request,
111                 const AceBasicRequest& request);
112     // Prompt validation
113     bool validatePopupResponse(
114                 const AceRequest& ace_request,
115                 const AceBasicRequest& request,
116                 bool answer = true,
117                 Prompt::Validity validity = Prompt::Validity::ALWAYS);
118     mutable PolicyInformationPoint m_pip;
119     DPL::Optional<std::set<DPL::String>> m_grantedDevCaps;
120     std::set<std::string> m_acceptedFeatures;
121 };
122
123 AceThinClientImpl::AceThinClientImpl()
124   : m_communicationClient(NULL),
125     m_popupValidationClient(NULL),
126     m_wrt(new WebRuntimeImpl()),
127     m_res(new ResourceInformationImpl()),
128     m_sys(new OperationSystemImpl()),
129     m_pip(m_wrt, m_res, m_sys)
130 {
131     AceDB::AceDAOReadOnly::attachToThreadRO();
132     Try {
133         m_communicationClient = new WrtSecurity::Communication::Client(WrtSecurity::AceServerApi::INTERFACE_NAME());
134         m_popupValidationClient = new WrtSecurity::Communication::Client(WrtSecurity::PopupServerApi::INTERFACE_NAME());
135     } Catch (WrtSecurity::Communication::Client::Exception::SecurityCommunicationClientException) {
136         if(m_communicationClient) delete m_communicationClient;
137         if(m_popupValidationClient) delete m_popupValidationClient;
138         delete m_wrt;
139         delete m_res;
140         delete m_sys;
141         ReThrowMsg(AceThinClient::Exception::AceThinClientException,
142                 "Failed to call security daemon");
143     }
144 }
145
146 AceThinClientImpl::~AceThinClientImpl()
147 {
148     Assert(NULL != m_communicationClient);
149     Assert(NULL != m_popupValidationClient);
150     delete m_communicationClient;
151     delete m_popupValidationClient;
152     delete m_wrt;
153     delete m_res;
154     delete m_sys;
155     m_communicationClient = NULL;
156     m_popupValidationClient = NULL;
157     AceDB::AceDAOReadOnly::detachFromThread();
158
159 }
160
161 bool AceThinClientImpl::isInitialized() const
162 {
163     return NULL != m_communicationClient && NULL != m_popupValidationClient;
164 }
165
166 bool AceThinClientImpl::containsNetworkDevCap(const AceRequest &ace_request)
167 {
168     AceDeviceCap deviceCap = ace_request.deviceCapabilities;
169     for (size_t j=0; j<deviceCap.devcapsCount; ++j) {
170         if (!deviceCap.devCapNames[j]) {
171             continue;
172         }
173         if (DEVCAP_XML_HTTP_REQUEST == deviceCap.devCapNames[j]
174             || DEVCAP_EXTERNAL_NETWORK_ACCESS == deviceCap.devCapNames[j])
175         {
176             return true;
177         }
178     }
179     return false;
180 }
181
182 bool AceThinClientImpl::checkFeatureList(const AceRequest& ace_request)
183 {
184     for (size_t i=0; i<ace_request.apiFeatures.count; ++i) {
185         Assert(ace_request.apiFeatures.apiFeature[i]);
186         std::string featureName(ace_request.apiFeatures.apiFeature[i]);
187         LogInfo("Api feature: " << featureName);
188         if (0 != m_acceptedFeatures.count(featureName)) {
189             return true;
190         }
191         LogInfo("Api-feature was not requested in widget config: " <<
192           featureName);
193     }
194     return false;
195 }
196
197 bool AceThinClientImpl::checkFunctionCall(const AceRequest& ace_request)
198 {
199     LogInfo("Enter");
200
201     // fill the m_grantedDevCaps, if not yet initialized
202     // TODO: This is not so pretty. AceThinClient is not explicitly
203     // tied to a widget handle, yet we assume it is always used
204     // with the same handle. This will be amended in a future
205     // refactoring (already planned).
206     if (m_grantedDevCaps.IsNull()) {
207         m_grantedDevCaps = std::set<DPL::String>();
208         m_acceptedFeatures.clear();
209
210         AceDB::FeatureNameVector fvector;
211         AceDB::AceDAOReadOnly::getAcceptedFeature(ace_request.widgetHandle, &fvector);
212         for(size_t i=0; i<fvector.size(); ++i) {
213             m_acceptedFeatures.insert(DPL::ToUTF8String(fvector[i]));
214          }
215     }
216
217     AceSubject subject = getSubjectForHandle(ace_request.widgetHandle);
218
219     // Create function params
220     const AceDeviceCap& devcaps = ace_request.deviceCapabilities;
221
222     LogInfo("Checking against config requested api-features.");
223
224     // Network device caps are not connected with api-features.
225     // We must pass empty api-feature when network dev cap is set.
226     if (!containsNetworkDevCap(ace_request) && !checkFeatureList(ace_request)) {
227         return false;
228     }
229
230     AceFunctionParams functionParams(devcaps.devcapsCount);
231     for (size_t i = 0; i < devcaps.devcapsCount; ++i) {
232         AceFunctionParam functionParam;
233         functionParam.addAttribute(AceFunctionParam::aceFunctionParamToken,
234                                    NULL == ace_request.functionName ?
235                                    "" : ace_request.functionName);
236         if (devcaps.paramsCount) {
237             Assert(devcaps.params);
238             for (size_t j = 0; j < devcaps.params[i].count; ++j) {
239                 Assert(devcaps.params[i].param &&
240                        devcaps.params[i].param[j].name &&
241                        devcaps.params[i].param[j].value);
242                 functionParam.addAttribute(
243                     std::string(devcaps.params[i].param[j].name),
244                     std::string(devcaps.params[i].param[j].value));
245             }
246         }
247         functionParams.push_back(functionParam);
248     }
249
250     // Convert AceRequest to array of AceBasicRequests
251     AceBasicRequests requests;
252
253     for (size_t i = 0; i < devcaps.devcapsCount; ++i) {
254         // Adding dev cap name here as resource id
255         Assert(devcaps.devCapNames[i]);
256         LogInfo("Device cap: " << devcaps.devCapNames[i]);
257         AceBasicRequest request(subject,
258                                 devcaps.devCapNames[i],
259                                 functionParams[i]);
260         requests.push_back(request);
261     }
262
263     // true means access granted, false - denied
264     bool result = true;
265
266     FOREACH(it, requests){
267         // Getting attributes from ACE DAO
268         AceBasicRequest& request = *it;
269         AceDB::BaseAttributeSet attributeSet;
270         AceDB::AceDAOReadOnly::getAttributes(&attributeSet);
271
272         // If true, we need to make popup IPC and ask user for decision
273         bool askPopup = false;
274         // If true, we need to make IPC to security daemon for policy
275         // decision on granting access
276         bool askServer = false;
277         // If askPopup == true, this is the kind of popup to  be opened
278         PolicyEffect popupType = PolicyEffect::PROMPT_ONESHOT;
279
280         if (attributeSet.empty()) {
281             // Treat this case as missed cache - ask security daemon
282             LogInfo("Empty attribute set");
283             askServer = true;
284         } else {
285             // Filling attributes with proper values
286             FunctionParamImpl params;
287             AceParamKeys keys = request.getFunctionParam().getKeys();
288             AceParamValues values = request.getFunctionParam().getValues();
289             for (size_t i = 0; i < keys.size(); ++i) {
290                 params.addAttribute(keys[i], values[i]);
291             }
292             Request req(ace_request.widgetHandle,
293                         WidgetExecutionPhase_Invoke,
294                         &params);
295             req.addDeviceCapability(request.getResourceId());
296
297             m_pip.getAttributesValues(&req, &attributeSet);
298
299             // Getting cached policy result
300             OptionalExtendedPolicyResult exPolicyResult =
301                     AceDB::AceDAOReadOnly::getPolicyResult(attributeSet);
302
303             if (exPolicyResult.IsNull()) {
304                 // Missed cache - ask security daemon
305                 LogInfo("Missed policy result cache");
306                 askServer = true;
307             } else {
308                 // Cached value found - now interpret it
309                 LogInfo("Result in cache");
310                 OptionalPolicyEffect effect = exPolicyResult->policyResult.getEffect();
311                 if (effect.IsNull()) {
312                     // PolicyDecision is UNDETERMINED or NOT_APPLICABLE
313                     result = false;
314                     break;
315                 } else if (*effect == PolicyEffect::DENY) {
316                     // Access denied
317                     result = false;
318                     break;
319                 } else if (*effect == PolicyEffect::PERMIT) {
320                     // Access granted
321                     if (m_grantedDevCaps->find(
322                            DPL::FromASCIIString(request.getResourceId()))
323                         != m_grantedDevCaps->end())
324                     {
325                         continue;
326                     } else
327                         askServer = true;
328                 } else {
329                     // Check for cached popup response
330                     LogInfo("Checking cached popup response");
331                     AceCachedPromptResult promptCached =
332                      getCachedPromptResult(ace_request.widgetHandle,
333                                            exPolicyResult->ruleId,
334                                            ace_request.sessionId);
335                     if (promptCached == AceCachedPromptResult::PERMIT) {
336                         // Granted by previous popup
337                         LogDebug("Cache found OK");
338                         if (m_grantedDevCaps->find(
339                                DPL::FromASCIIString(request.getResourceId()))
340                             != m_grantedDevCaps->end())
341                         {
342                             LogDebug("SMACK given previously");
343                             continue;
344                         } else {
345                             if (*effect != PolicyEffect::PROMPT_BLANKET) {
346                                 // This should not happen.
347                                 LogDebug("This should not happen.");
348                                 result = false;
349                                 break;
350                             }
351                             if (!validatePopupResponse(ace_request,
352                                                              request)) {
353                                 LogDebug("Daemon has not validated response.");
354                                 result = false;
355                                 break;
356                             } else {
357                                 // Access granted, move on to next request
358                                 LogDebug("SMACK granted, all OK");
359                                 m_grantedDevCaps->insert(
360                                     DPL::FromASCIIString(
361                                             request.getResourceId()));
362                                 continue;
363                             }
364                         }
365                     }
366                     if (promptCached == AceCachedPromptResult::DENY) {
367                         // Access denied by earlier popup
368                         result = false;
369                         break;
370                     }
371                     if (promptCached == AceCachedPromptResult::ASK_POPUP) {
372                         askPopup = true;
373                         popupType = *effect;
374                     }
375                 }
376             }
377         }
378
379         if (askServer) {
380             // IPC to security daemon
381             // here we must check if we have a SMACK permission for
382             // the device cap requested
383             LogInfo("Asking security daemon");
384             int serializedPolicyResult = 0;
385             Try {
386                 m_communicationClient->call(WrtSecurity::AceServerApi::CHECK_ACCESS_METHOD(),
387                                    ace_request.widgetHandle,
388                                    request.getSubjectId(),
389                                    request.getResourceId(),
390                                    request.getFunctionParam().getKeys(),
391                                    request.getFunctionParam().getValues(),
392                                    ace_request.sessionId,
393                                    &serializedPolicyResult);
394             } Catch (WrtSecurity::Communication::Client::Exception::SecurityCommunicationClientException) {
395                 ReThrowMsg(AceThinClient::Exception::AceThinClientException,
396                          "Failed to call security daemon");
397             }
398             PolicyResult policyResult = PolicyResult::
399                     deserialize(serializedPolicyResult);
400             OptionalPolicyEffect effect = policyResult.getEffect();
401             if (effect.IsNull()) {
402                 // PolicyDecision is UNDETERMINED or NOT_APPLICABLE
403                 result = false;
404                 break;
405             }
406             if (*effect == PolicyEffect::DENY) {
407                 // Access denied
408                 result = false;
409                 break;
410             }
411             if (*effect == PolicyEffect::PERMIT) {
412                 // Access granted, move on to next request
413                 m_grantedDevCaps->insert(
414                     DPL::FromASCIIString(request.getResourceId()));
415
416                 continue;
417             }
418             // Policy says: ask user - setup popup kind
419             popupType = *effect;
420             askPopup = true;
421         }
422
423         if (askPopup) {
424             result = askUser(popupType, ace_request, request);
425         }
426     }
427     LogInfo("Result: " << (result ? "GRANTED" : "DENIED"));
428     return result;
429 }
430
431 bool AceThinClientImpl::askUser(PolicyEffect popupType,
432                                 const AceRequest& ace_request,
433                                 const AceBasicRequest& request)
434 {
435     LogInfo("Asking popup");
436     Assert(NULL != popup_func);
437
438     const AceFunctionParam& fParam = request.getFunctionParam();
439     AceParamKeys keys = fParam.getKeys();
440     AceParamValues values = fParam.getValues();
441
442     ace_popup_t ace_popup_type;
443     ace_resource_t resource = const_cast<ace_session_id_t>(
444             request.getResourceId().c_str());
445     ace_session_id_t session = const_cast<ace_session_id_t>(
446             ace_request.sessionId.c_str());;
447     ace_param_list_t parameters;
448     ace_widget_handle_t handle = ace_request.widgetHandle;
449
450     parameters.count = keys.size();
451     parameters.items = new ace_param_t[parameters.count];
452     unsigned int i;
453     for (i = 0; i < parameters.count; ++i) {
454         parameters.items[i].name =
455                 const_cast<ace_string_t>(keys[i].c_str());
456         parameters.items[i].value =
457                 const_cast<ace_string_t>(values[i].c_str());
458     }
459
460     switch (popupType) {
461     case PolicyEffect::PROMPT_ONESHOT: {
462         ace_popup_type = ACE_ONESHOT;
463         break; }
464     case PolicyEffect::PROMPT_SESSION: {
465         ace_popup_type = ACE_SESSION;
466         break; }
467     case PolicyEffect::PROMPT_BLANKET: {
468         ace_popup_type = ACE_BLANKET;
469         break; }
470     default: {
471         LogError("Unknown popup type passed!");
472         LogError("Maybe effect isn't a popup?");
473         LogError("Effect number is: " << static_cast<int>(popupType));
474         Assert(0); }
475     }
476
477     ace_bool_t answer = ACE_FALSE;
478     ace_return_t ret = popup_func(ace_popup_type,
479                     resource,
480                     session,
481                     &parameters,
482                     handle,
483                     &answer);
484
485     delete [] parameters.items;
486
487     if (ACE_OK != ret) {
488         LogError("Error in popup handler");
489         return false;
490     }
491
492     if (ACE_TRUE == answer) {
493         m_grantedDevCaps->insert(
494             DPL::FromASCIIString(request.getResourceId()));
495         return true;
496     }
497
498     return false;
499 }
500
501 bool AceThinClientImpl::validatePopupResponse(
502         const AceRequest& ace_request,
503         const AceBasicRequest& request,
504         bool answer,
505         Prompt::Validity validity
506         )
507 {
508     bool response = false;
509     Try{
510         m_popupValidationClient->call(
511                            WrtSecurity::PopupServerApi::VALIDATION_METHOD(),
512                            answer,
513                            static_cast<int>(validity),
514                            ace_request.widgetHandle,
515                            request.getSubjectId(),
516                            request.getResourceId(),
517                            request.getFunctionParam().getKeys(),
518                            request.getFunctionParam().getValues(),
519                            ace_request.sessionId,
520                            &response);
521     } Catch (WrtSecurity::Communication::Client::Exception::SecurityCommunicationClientException) {
522         ReThrowMsg(AceThinClient::Exception::AceThinClientException,
523                  "Failed to call security daemon");
524     }
525     return response;
526 }
527
528 AcePreference AceThinClientImpl::getWidgetResourcePreference (
529         const AceResource& resource,
530         const AceWidgetHandle& handle) const
531 {
532     return toAcePreference(
533             AceDB::AceDAOReadOnly::getWidgetDevCapSetting(resource, handle));
534 }
535
536 AceResourcesPreferences* AceThinClientImpl::getGlobalResourcesPreferences()
537 const
538 {
539     AceDB::PreferenceTypesMap globalSettingsMap;
540     AceResourcesPreferences* acePreferences = new AceResourcesPreferences();
541     AceDB::AceDAOReadOnly::getDevCapSettings(&globalSettingsMap);
542     FOREACH(it, globalSettingsMap) {
543         acePreferences->insert(
544                 AceResurcePreference((*it).first,
545                         toAcePreference((*it).second)));
546     }
547     return acePreferences;
548 }
549
550 AceSubject AceThinClientImpl::getSubjectForHandle(AceWidgetHandle handle) const
551 {
552     try
553     {
554         return AceDB::AceDAOReadOnly::getGUID(handle);
555     }
556     catch (AceDB::AceDAOReadOnly::Exception::DatabaseError& /*ex*/)
557     {
558         LogError("Couldn't find GIUD for handle " << handle);
559         return "";
560     }
561 }
562
563 AceCachedPromptResult AceThinClientImpl::getCachedPromptResult(
564         WidgetHandle widgetHandle,
565         int ruleId,
566         const AceSessionId& sessionId) const
567 {
568     OptionalCachedPromptDecision promptDecision =
569     AceDB::AceDAOReadOnly::getPromptDecision(
570             widgetHandle,
571             ruleId);
572     if (promptDecision.IsNull()) {
573         LogDebug("No cache");
574         return AceCachedPromptResult::ASK_POPUP;
575     } else {
576         // These should not be stored in DB!
577         Assert(PromptDecision::ALLOW_THIS_TIME
578                 != (*promptDecision).decision);
579         Assert(PromptDecision::DENY_THIS_TIME
580                 != (*promptDecision).decision);
581         if ((*promptDecision).decision ==
582                 PromptDecision::ALLOW_ALWAYS) {
583             // Access granted via earlier popup
584             LogDebug("ALLOW_ALWAYS");
585             return AceCachedPromptResult::PERMIT;
586         }
587         if ((*promptDecision).decision ==
588                 PromptDecision::DENY_ALWAYS) {
589             LogDebug("DENY_ALWAYS");
590             // Access denied via earlier popup
591             return AceCachedPromptResult::DENY;
592         }
593         // Only thing left is per session prompts
594         if ((*promptDecision).session.IsNull()) {
595             LogDebug("NO SESSION");
596             return AceCachedPromptResult::ASK_POPUP;
597         }
598         AceSessionId cachedSessionId = DPL::ToUTF8String(*((*promptDecision).session));
599         if ((*promptDecision).decision ==
600                 PromptDecision::ALLOW_FOR_SESSION) {
601             if (cachedSessionId == sessionId) {
602                 // Access granted for this session.
603                 LogDebug("SESSION OK, PERMIT");
604                 return AceCachedPromptResult::PERMIT;
605             } else {
606                 LogDebug("SESSION NOT OK, ASKING");
607                 return AceCachedPromptResult::ASK_POPUP;
608             }
609         }
610         if ((*promptDecision).decision ==
611                 PromptDecision::DENY_FOR_SESSION) {
612             if (cachedSessionId == sessionId) {
613                 // Access denied for this session.
614                 LogDebug("SESSION OK, DENY");
615                 return AceCachedPromptResult::DENY;
616             } else {
617                 LogDebug("SESSION NOT OK, ASKING");
618                 return AceCachedPromptResult::ASK_POPUP;
619             }
620         }
621     }
622     LogDebug("NO RESULT, ASKING");
623     return AceCachedPromptResult::ASK_POPUP;
624 }
625
626 // AceThinClient
627
628 bool AceThinClient::checkFunctionCall(
629         const AceRequest& ace_request) const
630 {
631     return m_impl->checkFunctionCall(ace_request);
632 }
633
634 AcePreference AceThinClient::getWidgetResourcePreference(
635         const AceResource& resource,
636         const AceWidgetHandle& handle) const
637 {
638     return m_impl->getWidgetResourcePreference(
639             resource, handle);
640 }
641
642 AceResourcesPreferences* AceThinClient::getGlobalResourcesPreferences()
643 const
644 {
645     return m_impl->getGlobalResourcesPreferences();
646 }
647
648 AceThinClient::AceThinClient()
649 {
650     m_impl = new AceThinClientImpl();
651 }
652
653 AceThinClient::~AceThinClient()
654 {
655     Assert(NULL != m_impl);
656     delete m_impl;
657 }
658
659 bool AceThinClient::isInitialized() const
660 {
661     return NULL != m_impl && m_impl->isInitialized();
662 }
663
664
665 } // namespace AceClient