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