1 //*********************************************************
2 //
3 //    Copyright (c) Microsoft. All rights reserved.
4 //    This code is licensed under the MIT License.
5 //    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
6 //    ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
7 //    TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
8 //    PARTICULAR PURPOSE AND NONINFRINGEMENT.
9 //
10 //*********************************************************
11 #ifndef __WIL_TOKEN_HELPERS_INCLUDED
12 #define __WIL_TOKEN_HELPERS_INCLUDED
13 
14 #ifdef _KERNEL_MODE
15 #error This header is not supported in kernel-mode.
16 #endif
17 
18 #include "resource.h"
19 #include <new>
20 #include <lmcons.h>         // for UNLEN and DNLEN
21 #include <processthreadsapi.h>
22 
23 // for GetUserNameEx()
24 #define SECURITY_WIN32
25 #include <Security.h>
26 
27 namespace wil
28 {
29     /// @cond
30     namespace details
31     {
32         // Template specialization for TOKEN_INFORMATION_CLASS, add more mappings here as needed
33         // TODO: The mapping should be reversed to be MapTokenInfoClassToStruct since there may
34         // be an info class value that uses the same structure. That is the case for the file
35         // system information.
36         template<typename T> struct MapTokenStructToInfoClass;
37         template<> struct MapTokenStructToInfoClass<TOKEN_ACCESS_INFORMATION> { static const TOKEN_INFORMATION_CLASS infoClass = TokenAccessInformation; static const bool FixedSize = false; };
38         template<> struct MapTokenStructToInfoClass<TOKEN_APPCONTAINER_INFORMATION> { static const TOKEN_INFORMATION_CLASS infoClass = TokenAppContainerSid; static const bool FixedSize = false; };
39         template<> struct MapTokenStructToInfoClass<TOKEN_DEFAULT_DACL> { static const TOKEN_INFORMATION_CLASS infoClass = TokenDefaultDacl; static const bool FixedSize = false; };
40         template<> struct MapTokenStructToInfoClass<TOKEN_GROUPS_AND_PRIVILEGES> { static const TOKEN_INFORMATION_CLASS infoClass = TokenGroupsAndPrivileges; static const bool FixedSize = false; };
41         template<> struct MapTokenStructToInfoClass<TOKEN_MANDATORY_LABEL> { static const TOKEN_INFORMATION_CLASS infoClass = TokenIntegrityLevel; static const bool FixedSize = false; };
42         template<> struct MapTokenStructToInfoClass<TOKEN_OWNER> { static const TOKEN_INFORMATION_CLASS infoClass = TokenOwner; static const bool FixedSize = false;  };
43         template<> struct MapTokenStructToInfoClass<TOKEN_PRIMARY_GROUP> { static const TOKEN_INFORMATION_CLASS infoClass = TokenPrimaryGroup; static const bool FixedSize = false;  };
44         template<> struct MapTokenStructToInfoClass<TOKEN_PRIVILEGES> { static const TOKEN_INFORMATION_CLASS infoClass = TokenPrivileges; static const bool FixedSize = false;  };
45         template<> struct MapTokenStructToInfoClass<TOKEN_USER> { static const TOKEN_INFORMATION_CLASS infoClass = TokenUser; static const bool FixedSize = false;  };
46 
47         // fixed size cases
48         template<> struct MapTokenStructToInfoClass<TOKEN_ELEVATION_TYPE> { static const TOKEN_INFORMATION_CLASS infoClass = TokenElevationType; static const bool FixedSize = true; };
49         template<> struct MapTokenStructToInfoClass<TOKEN_MANDATORY_POLICY> { static const TOKEN_INFORMATION_CLASS infoClass = TokenMandatoryPolicy; static const bool FixedSize = true; };
50         template<> struct MapTokenStructToInfoClass<TOKEN_ORIGIN> { static const TOKEN_INFORMATION_CLASS infoClass = TokenOrigin; static const bool FixedSize = true; };
51         template<> struct MapTokenStructToInfoClass<TOKEN_SOURCE> { static const TOKEN_INFORMATION_CLASS infoClass = TokenSource; static const bool FixedSize = true; };
52         template<> struct MapTokenStructToInfoClass<TOKEN_STATISTICS> { static const TOKEN_INFORMATION_CLASS infoClass = TokenStatistics; static const bool FixedSize = true; };
53         template<> struct MapTokenStructToInfoClass<TOKEN_TYPE> { static const TOKEN_INFORMATION_CLASS infoClass = TokenType; static const bool FixedSize = true; };
54         template<> struct MapTokenStructToInfoClass<SECURITY_IMPERSONATION_LEVEL> { static const TOKEN_INFORMATION_CLASS infoClass = TokenImpersonationLevel;  static const bool FixedSize = true; };
55         template<> struct MapTokenStructToInfoClass<TOKEN_ELEVATION> { static const TOKEN_INFORMATION_CLASS infoClass = TokenElevation; static const bool FixedSize = true; };
56     }
57     /// @endcond
58 
59     enum class OpenThreadTokenAs
60     {
61         Current,
62         Self
63     };
64 
65     /** Open the active token.
66     Opens either the current thread token (if impersonating) or the current process token. Returns a token the caller
67     can use with methods like get_token_information<> below. By default, the token is opened for TOKEN_QUERY and as the
68     effective user.
69 
70     Consider using GetCurrentThreadEffectiveToken() instead of this method when eventually calling get_token_information.
71     This method returns a real handle to the effective token, but GetCurrentThreadEffectiveToken() is a Pseudo-handle
72     and much easier to manage.
73     ~~~~
74     wil::unique_handle theToken;
75     RETURN_IF_FAILED(wil::open_current_access_token_nothrow(&theToken));
76     ~~~~
77     Callers who want more access to the token (such as to duplicate or modify the token) can pass
78     any mask of the token rights.
79     ~~~~
80     wil::unique_handle theToken;
81     RETURN_IF_FAILED(wil::open_current_access_token_nothrow(&theToken, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES));
82     ~~~~
83     Services impersonating their clients may need to request that the active token is opened on the
84     behalf of the service process to perform certain operations. Opening a token for impersonation access
85     or privilege-adjustment are examples of uses.
86     ~~~~
87     wil::unique_handle callerToken;
88     RETURN_IF_FAILED(wil::open_current_access_token_nothrow(&theToken, TOKEN_QUERY | TOKEN_IMPERSONATE, true));
89     ~~~~
90     @param tokenHandle Receives the token opened during the operation. Must be CloseHandle'd by the caller, or
91                 (preferably) stored in a wil::unique_handle
92     @param access Bits from the TOKEN_* access mask which are passed to OpenThreadToken/OpenProcessToken
93     @param asSelf When true, and if the thread is impersonating, the thread token is opened using the
94                 process token's rights.
95     */
96     inline HRESULT open_current_access_token_nothrow(_Out_ HANDLE* tokenHandle, unsigned long access = TOKEN_QUERY, OpenThreadTokenAs openAs = OpenThreadTokenAs::Current)
97     {
98         HRESULT hr = (OpenThreadToken(GetCurrentThread(), access, (openAs == OpenThreadTokenAs::Self), tokenHandle) ? S_OK : HRESULT_FROM_WIN32(::GetLastError()));
99         if (hr == HRESULT_FROM_WIN32(ERROR_NO_TOKEN))
100         {
101             hr = (OpenProcessToken(GetCurrentProcess(), access, tokenHandle) ? S_OK : HRESULT_FROM_WIN32(::GetLastError()));
102         }
103         return hr;
104     }
105 
106     //! Current thread or process token, consider using GetCurrentThreadEffectiveToken() instead.
107     inline wil::unique_handle open_current_access_token_failfast(unsigned long access = TOKEN_QUERY, OpenThreadTokenAs openAs = OpenThreadTokenAs::Current)
108     {
109         HANDLE rawTokenHandle;
110         FAIL_FAST_IF_FAILED(open_current_access_token_nothrow(&rawTokenHandle, access, openAs));
111         return wil::unique_handle(rawTokenHandle);
112     }
113 
114 // Exception based function to open current thread/process access token and acquire pointer to it
115 #ifdef WIL_ENABLE_EXCEPTIONS
116     //! Current thread or process token, consider using GetCurrentThreadEffectiveToken() instead.
117     inline wil::unique_handle open_current_access_token(unsigned long access = TOKEN_QUERY, OpenThreadTokenAs openAs = OpenThreadTokenAs::Current)
118     {
119         HANDLE rawTokenHandle;
120         THROW_IF_FAILED(open_current_access_token_nothrow(&rawTokenHandle, access, openAs));
121         return wil::unique_handle(rawTokenHandle);
122     }
123 #endif // WIL_ENABLE_EXCEPTIONS
124 
125     // Returns tokenHandle or the effective thread token if tokenHandle is null.
126     // Note, this returns an token handle who's lifetime is managed independently
127     // and it may be a pseudo token, don't free it!
128     inline HANDLE GetCurrentThreadEffectiveTokenWithOverride(HANDLE tokenHandle)
129     {
130         return tokenHandle ? tokenHandle : GetCurrentThreadEffectiveToken();
131     }
132 
133     /** Fetches information about a token.
134     See GetTokenInformation on MSDN for what this method can return. For variable sized structs the information
135     is returned to the caller as a wistd::unique_ptr<T> (like TOKEN_ORIGIN, TOKEN_USER, TOKEN_ELEVATION, etc.). For
136     fixed sized, the struct is returned directly.
137     The caller must have access to read the information from the provided token. This method works with both real
138     (e.g. OpenCurrentAccessToken) and pseudo (e.g. GetCurrentThreadToken) token handles.
139     ~~~~
140     // Retrieve the TOKEN_USER structure for the current process
141     wistd::unique_ptr<TOKEN_USER> user;
142     RETURN_IF_FAILED(wil::get_token_information_nothrow(user, GetCurrentProcessToken()));
143     RETURN_IF_FAILED(ConsumeSid(user->User.Sid));
144     ~~~~
145     Not specifying the token handle is the same as specifying 'nullptr' and retrieves information about the effective token.
146     ~~~~
147     wistd::unique_ptr<TOKEN_PRIVILEGES> privileges;
148     RETURN_IF_FAILED(wil::get_token_information_nothrow(privileges));
149     for (auto const& privilege : wil::GetRange(privileges->Privileges, privileges->PrivilegeCount))
150     {
151         RETURN_IF_FAILED(ConsumePrivilege(privilege));
152     }
153     ~~~~
154     @param tokenInfo Receives a pointer to a structure containing the results of GetTokenInformation for the requested
155             type. The type of <T> selects which TOKEN_INFORMATION_CLASS will be used.
156     @param tokenHandle Specifies which token will be queried. When nullptr, the thread's effective current token is used.
157     @return S_OK on success, a FAILED hresult containing the win32 error from querying the token otherwise.
158     */
159 
160     template <typename T, wistd::enable_if_t<!details::MapTokenStructToInfoClass<T>::FixedSize>* = nullptr>
161     inline HRESULT get_token_information_nothrow(wistd::unique_ptr<T>& tokenInfo, HANDLE tokenHandle = nullptr)
162     {
163         tokenInfo.reset();
164         tokenHandle = GetCurrentThreadEffectiveTokenWithOverride(tokenHandle);
165 
166         DWORD tokenInfoSize = 0;
167         const auto infoClass = details::MapTokenStructToInfoClass<T>::infoClass;
168         RETURN_LAST_ERROR_IF(!((!GetTokenInformation(tokenHandle, infoClass, nullptr, 0, &tokenInfoSize)) &&
169             (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)));
170         wistd::unique_ptr<char> tokenInfoClose(
171             static_cast<char*>(operator new(tokenInfoSize, std::nothrow)));
172         RETURN_IF_NULL_ALLOC(tokenInfoClose.get());
173         RETURN_IF_WIN32_BOOL_FALSE(GetTokenInformation(tokenHandle, infoClass, tokenInfoClose.get(), tokenInfoSize, &tokenInfoSize));
174         tokenInfo.reset(reinterpret_cast<T *>(tokenInfoClose.release()));
175 
176         return S_OK;
177     }
178 
179     template <typename T, wistd::enable_if_t<details::MapTokenStructToInfoClass<T>::FixedSize>* = nullptr>
180     inline HRESULT get_token_information_nothrow(_Out_ T* tokenInfo, HANDLE tokenHandle = nullptr)
181     {
182         *tokenInfo = {};
183         tokenHandle = GetCurrentThreadEffectiveTokenWithOverride(tokenHandle);
184 
185         DWORD tokenInfoSize = sizeof(T);
186         const auto infoClass = details::MapTokenStructToInfoClass<T>::infoClass;
187         RETURN_IF_WIN32_BOOL_FALSE(GetTokenInformation(tokenHandle, infoClass, tokenInfo, tokenInfoSize, &tokenInfoSize));
188 
189         return S_OK;
190     }
191 
192     namespace details
193     {
194         template<typename T, typename policy, wistd::enable_if_t<!details::MapTokenStructToInfoClass<T>::FixedSize>* = nullptr>
195         wistd::unique_ptr<T> GetTokenInfoWrap(HANDLE token = nullptr)
196         {
197             wistd::unique_ptr<T> temp;
198             policy::HResult(get_token_information_nothrow(temp, token));
199             return temp;
200         }
201 
202         template<typename T, typename policy, wistd::enable_if_t<details::MapTokenStructToInfoClass<T>::FixedSize>* = nullptr>
203         T GetTokenInfoWrap(HANDLE token = nullptr)
204         {
205             T temp{};
206             policy::HResult(get_token_information_nothrow(&temp, token));
207             return temp;
208         }
209     }
210 
211     //! A variant of get_token_information<T> that fails-fast on errors retrieving the token
212     template <typename T>
213     inline auto get_token_information_failfast(HANDLE token = nullptr)
214     {
215         return details::GetTokenInfoWrap<T, err_failfast_policy>(token);
216     }
217 
218     //! Overload of GetTokenInformationNoThrow that retrieves a token linked from the provided token
219     inline HRESULT get_token_information_nothrow(unique_token_linked_token& tokenInfo, HANDLE tokenHandle = nullptr)
220     {
221         static_assert(sizeof(tokenInfo) == sizeof(TOKEN_LINKED_TOKEN), "confusing size mismatch");
222         tokenHandle = GetCurrentThreadEffectiveTokenWithOverride(tokenHandle);
223 
224         DWORD tokenInfoSize = 0;
225         RETURN_IF_WIN32_BOOL_FALSE(::GetTokenInformation(tokenHandle, TokenLinkedToken,
226             tokenInfo.reset_and_addressof(), sizeof(tokenInfo), &tokenInfoSize));
227         return S_OK;
228     }
229 
230     /** Retrieves the linked-token information for a token.
231     Fails-fast if the link information cannot be retrieved.
232     ~~~~
233     auto link = get_linked_token_information_failfast(GetCurrentThreadToken());
234     auto tokenUser = get_token_information<TOKEN_USER>(link.LinkedToken);
235     ~~~~
236     @param token Specifies the token to query. Pass nullptr to use the current effective thread token
237     @return unique_token_linked_token containing a handle to the linked token
238     */
239     inline unique_token_linked_token get_linked_token_information_failfast(HANDLE token = nullptr)
240     {
241         unique_token_linked_token tokenInfo;
242         FAIL_FAST_IF_FAILED(get_token_information_nothrow(tokenInfo, token));
243         return tokenInfo;
244     }
245 
246 #ifdef WIL_ENABLE_EXCEPTIONS
247     /** Fetches information about a token.
248     See get_token_information_nothrow for full details.
249     ~~~~
250     auto user = wil::get_token_information<TOKEN_USER>(GetCurrentProcessToken());
251     ConsumeSid(user->User.Sid);
252     ~~~~
253     Pass 'nullptr' (or omit the parameter) as tokenHandle to retrieve information about the effective token.
254     ~~~~
255     auto privs = wil::get_token_information<TOKEN_PRIVILEGES>(privileges);
256     for (auto& priv : wil::make_range(privs->Privileges, privs->Privilieges + privs->PrivilegeCount))
257     {
258         if (priv.Attributes & SE_PRIVILEGE_ENABLED)
259         {
260             // ...
261         }
262     }
263     ~~~~
264     @return A pointer to a structure containing the results of GetTokenInformation for the requested  type. The type of
265                 <T> selects which TOKEN_INFORMATION_CLASS will be used.
266     @param token Specifies which token will be queried. When nullptr or not set, the thread's effective current token is used.
267     */
268     template <typename T>
269     inline auto get_token_information(HANDLE token = nullptr)
270     {
271         return details::GetTokenInfoWrap<T, err_exception_policy>(token);
272     }
273 
274     /** Retrieves the linked-token information for a token.
275     Throws an exception if the link information cannot be retrieved.
276     ~~~~
277     auto link = get_linked_token_information(GetCurrentThreadToken());
278     auto tokenUser = get_token_information<TOKEN_USER>(link.LinkedToken);
279     ~~~~
280     @param token Specifies the token to query. Pass nullptr to use the current effective thread token
281     @return unique_token_linked_token containing a handle to the linked token
282     */
283     inline unique_token_linked_token get_linked_token_information(HANDLE token = nullptr)
284     {
285         unique_token_linked_token tokenInfo;
286         THROW_IF_FAILED(get_token_information_nothrow(tokenInfo, token));
287         return tokenInfo;
288     }
289 #endif
290 
291     /// @cond
292     namespace details
293     {
294         inline void RevertImpersonateToken(_Pre_opt_valid_ _Frees_ptr_opt_ HANDLE oldToken)
295         {
296             FAIL_FAST_IMMEDIATE_IF(!::SetThreadToken(nullptr, oldToken));
297 
298             if (oldToken)
299             {
300                 ::CloseHandle(oldToken);
301             }
302         }
303     }
304     /// @endcond
305 
306     using unique_token_reverter = wil::unique_any<
307         HANDLE,
308         decltype(&details::RevertImpersonateToken),
309         details::RevertImpersonateToken,
310         details::pointer_access_none,
311         HANDLE,
312         INT_PTR,
313         -1,
314         HANDLE>;
315 
316     /** Temporarily impersonates a token on this thread.
317     This method sets a new token on a thread, restoring the current token when the returned object
318     is destroyed. Useful for impersonating other tokens or running as 'self,' especially in services.
319     ~~~~
320     HRESULT OpenFileAsSessionuser(PCWSTR filePath, DWORD session, _Out_ HANDLE* opened)
321     {
322         wil::unique_handle userToken;
323         RETURN_IF_WIN32_BOOL_FALSE(QueryUserToken(session, &userToken));
324 
325         wil::unique_token_reverter reverter;
326         RETURN_IF_FAILED(wil::impersonate_token_nothrow(userToken.get(), reverter));
327 
328         wil::unique_hfile userFile(::CreateFile(filePath, ...));
329         RETURN_LAST_ERROR_IF(!userFile && (::GetLastError() != ERROR_FILE_NOT_FOUND));
330 
331         *opened = userFile.release();
332         return S_OK;
333     }
334     ~~~~
335     @param token A token to impersonate, or 'nullptr' to run as the process identity.
336     */
337     inline HRESULT impersonate_token_nothrow(HANDLE token, unique_token_reverter& reverter)
338     {
339         wil::unique_handle currentToken;
340 
341         // Get the token for the current thread. If there wasn't one, the reset will clear it as well
342         if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &currentToken))
343         {
344             RETURN_LAST_ERROR_IF(::GetLastError() != ERROR_NO_TOKEN);
345         }
346 
347         // Update the current token
348         RETURN_IF_WIN32_BOOL_FALSE(::SetThreadToken(nullptr, token));
349 
350         reverter.reset(currentToken.release()); // Ownership passed
351         return S_OK;
352     }
353 
354     /** Temporarily clears any impersonation on this thread.
355     This method resets the current thread's token to nullptr, indicating that it is not impersonating
356     any user. Useful for elevating to whatever identity a service or higher-privilege process might
357     be capable of running under.
358     ~~~~
359     HRESULT DeleteFileRetryAsSelf(PCWSTR filePath)
360     {
361         if (!::DeleteFile(filePath))
362         {
363             RETURN_LAST_ERROR_IF(::GetLastError() != ERROR_ACCESS_DENIED);
364             wil::unique_token_reverter reverter;
365             RETURN_IF_FAILED(wil::run_as_self_nothrow(reverter));
366             RETURN_IF_FAILED(TakeOwnershipOfFile(filePath));
367             RETURN_IF_FAILED(GrantDeleteAccess(filePath));
368             RETURN_IF_WIN32_BOOL_FALSE(::DeleteFile(filePath));
369         }
370         return S_OK;
371     }
372     ~~~~
373     */
374     inline HRESULT run_as_self_nothrow(unique_token_reverter& reverter)
375     {
376         return impersonate_token_nothrow(nullptr, reverter);
377     }
378 
379     inline unique_token_reverter impersonate_token_failfast(HANDLE token)
380     {
381         unique_token_reverter oldToken;
382         FAIL_FAST_IF_FAILED(impersonate_token_nothrow(token, oldToken));
383         return oldToken;
384     }
385 
386     inline unique_token_reverter run_as_self_failfast()
387     {
388         return impersonate_token_failfast(nullptr);
389     }
390 
391 #ifdef WIL_ENABLE_EXCEPTIONS
392     /** Temporarily impersonates a token on this thread.
393     This method sets a new token on a thread, restoring the current token when the returned object
394     is destroyed. Useful for impersonating other tokens or running as 'self,' especially in services.
395     ~~~~
396     wil::unique_hfile OpenFileAsSessionuser(_In_z_ const wchar_t* filePath, DWORD session)
397     {
398         wil::unique_handle userToken;
399         THROW_IF_WIN32_BOOL_FALSE(QueryUserToken(session, &userToken));
400 
401         auto priorToken = wil::impersonate_token(userToken.get());
402 
403         wil::unique_hfile userFile(::CreateFile(filePath, ...));
404         THROW_LAST_ERROR_IF(::GetLastError() != ERROR_FILE_NOT_FOUND);
405 
406         return userFile;
407     }
408     ~~~~
409     @param token A token to impersonate, or 'nullptr' to run as the process identity.
410     */
411     inline unique_token_reverter impersonate_token(HANDLE token = nullptr)
412     {
413         unique_token_reverter oldToken;
414         THROW_IF_FAILED(impersonate_token_nothrow(token, oldToken));
415         return oldToken;
416     }
417 
418     /** Temporarily clears any impersonation on this thread.
419     This method resets the current thread's token to nullptr, indicating that it is not impersonating
420     any user. Useful for elevating to whatever identity a service or higher-privilege process might
421     be capable of running under.
422     ~~~~
423     void DeleteFileRetryAsSelf(_In_z_ const wchar_t* filePath)
424     {
425         if (!::DeleteFile(filePath) && (::GetLastError() == ERROR_ACCESS_DENIED))
426         {
427             auto priorToken = wil::run_as_self();
428             TakeOwnershipOfFile(filePath);
429             GrantDeleteAccess(filePath);
430             ::DeleteFile(filePath);
431         }
432     }
433     ~~~~
434     */
435     inline unique_token_reverter run_as_self()
436     {
437         return impersonate_token(nullptr);
438     }
439 #endif // WIL_ENABLE_EXCEPTIONS
440 
441     namespace details
442     {
443         template<size_t AuthorityCount> struct static_sid_t
444         {
445             BYTE Revision;
446             BYTE SubAuthorityCount;
447             SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
448             DWORD SubAuthority[AuthorityCount];
449 
450             PSID get()
451             {
452                 return reinterpret_cast<PSID>(this);
453             }
454 
455             template<size_t other> static_sid_t& operator=(const static_sid_t<other>& source)
456             {
457                 static_assert(other <= AuthorityCount, "Cannot assign from a larger static sid to a smaller one");
458 
459                 if (&this->Revision != &source.Revision)
460                 {
461                     memcpy(this, &source, sizeof(source));
462                 }
463 
464                 return *this;
465             }
466         };
467     }
468 
469     /** Returns a structure containing a Revision 1 SID initialized with the authorities provided
470     Replaces AllocateAndInitializeSid by constructing a structure laid out like a PSID, but
471     returned like a value. The resulting object is suitable for use with any method taking PSID,
472     passed by "&the_sid" or via "the_sid.get()"
473     ~~~~
474     // Change the owner of the key to administrators
475     auto systemSid = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
476     RETURN_IF_WIN32_ERROR(SetNamedSecurityInfo(keyPath, SE_REGISTRY_KEY, OWNER_SECURITY_INFORMATION, &systemSid, nullptr, nullptr, nullptr));
477     ~~~~
478     */
479     template<typename... Ts> constexpr auto make_static_sid(const SID_IDENTIFIER_AUTHORITY& authority, Ts&&... subAuthorities)
480     {
481         using sid_t = details::static_sid_t<sizeof...(subAuthorities)>;
482 
483         static_assert(sizeof...(subAuthorities) <= SID_MAX_SUB_AUTHORITIES, "too many sub authorities");
484         static_assert(offsetof(sid_t, Revision) == offsetof(_SID, Revision), "layout mismatch");
485         static_assert(offsetof(sid_t, SubAuthorityCount) == offsetof(_SID, SubAuthorityCount), "layout mismatch");
486         static_assert(offsetof(sid_t, IdentifierAuthority) == offsetof(_SID, IdentifierAuthority), "layout mismatch");
487         static_assert(offsetof(sid_t, SubAuthority) == offsetof(_SID, SubAuthority), "layout mismatch");
488 
489         return sid_t { SID_REVISION, sizeof...(subAuthorities), authority, { static_cast<DWORD>(subAuthorities)... } };
490     }
491 
492     //! Variant of static_sid that defaults to the NT authority
493     template<typename... Ts> constexpr auto make_static_nt_sid(Ts&& ... subAuthorities)
494     {
495         return make_static_sid(SECURITY_NT_AUTHORITY, wistd::forward<Ts>(subAuthorities)...);
496     }
497 
498     /** Determines whether a specified security identifier (SID) is enabled in an access token.
499     This function determines whether a security identifier, described by a given set of subauthorities, is enabled
500     in the given access token. Note that only up to eight subauthorities can be passed to this function.
501     ~~~~
502     bool IsGuest()
503     {
504         return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_GUESTS));
505     }
506     ~~~~
507     @param result This will be set to true if and only if a security identifier described by the given set of subauthorities is enabled in the given access token.
508     @param token A handle to an access token. The handle must have TOKEN_QUERY access to the token, and must be an impersonation token. If token is nullptr, test_token_membership
509            uses the impersonation token of the calling thread. If the thread is not impersonating, the function duplicates the thread's primary token to create an impersonation token.
510     @param sidAuthority A reference to a SID_IDENTIFIER_AUTHORITY structure. This structure provides the top-level identifier authority value to set in the SID.
511     @param subAuthorities Up to 15 subauthority values to place in the SID (this is a systemwide limit)
512     @return S_OK on success, a FAILED hresult containing the win32 error from creating the SID or querying the token otherwise.
513     */
514     template<typename... Ts> HRESULT test_token_membership_nothrow(_Out_ bool* result, _In_opt_ HANDLE token,
515         const SID_IDENTIFIER_AUTHORITY& sidAuthority, Ts&&... subAuthorities)
516     {
517         *result = false;
518         auto tempSid = make_static_sid(sidAuthority, wistd::forward<Ts>(subAuthorities)...);
519         BOOL isMember;
520         RETURN_IF_WIN32_BOOL_FALSE(CheckTokenMembership(token, &tempSid, &isMember));
521 
522         *result = (isMember != FALSE);
523 
524         return S_OK;
525     }
526 
527     /** Determine whether a token represents an app container
528     This method uses the passed in token and emits a boolean indicating that
529     whether TokenIsAppContainer is true.
530     ~~~~
531     HRESULT OnlyIfAppContainer()
532     {
533     bool isAppContainer;
534     RETURN_IF_FAILED(wil::get_token_is_app_container_nothrow(nullptr, isAppContainer));
535     RETURN_HR_IF(E_ACCESSDENIED, !isAppContainer);
536     RETURN_HR(...);
537     }
538     ~~~~
539     @param token A token to get info about, or 'nullptr' to run as the current thread.
540     */
541     inline HRESULT get_token_is_app_container_nothrow(_In_opt_ HANDLE token, bool& value)
542     {
543         DWORD isAppContainer = 0;
544         DWORD returnLength = 0;
545         RETURN_IF_WIN32_BOOL_FALSE(::GetTokenInformation(
546             token ? token : GetCurrentThreadEffectiveToken(),
547             TokenIsAppContainer,
548             &isAppContainer,
549             sizeof(isAppContainer),
550             &returnLength));
551 
552         value = (isAppContainer != 0);
553 
554         return S_OK;
555     }
556 
557     //! A variant of get_token_is_app_container_nothrow that fails-fast on errors retrieving the token information
558     inline bool get_token_is_app_container_failfast(HANDLE token = nullptr)
559     {
560         bool value = false;
561         FAIL_FAST_IF_FAILED(get_token_is_app_container_nothrow(token, value));
562 
563         return value;
564     }
565 
566 #ifdef WIL_ENABLE_EXCEPTIONS
567     //! A variant of get_token_is_app_container_nothrow that throws on errors retrieving the token information
568     inline bool get_token_is_app_container(HANDLE token = nullptr)
569     {
570         bool value = false;
571         THROW_IF_FAILED(get_token_is_app_container_nothrow(token, value));
572 
573         return value;
574     }
575 #endif // WIL_ENABLE_EXCEPTIONS
576 
577     template<typename... Ts> bool test_token_membership_failfast(_In_opt_ HANDLE token,
578         const SID_IDENTIFIER_AUTHORITY& sidAuthority, Ts&&... subAuthorities)
579     {
580         bool result;
581         FAIL_FAST_IF_FAILED(test_token_membership_nothrow(&result, token, sidAuthority, wistd::forward<Ts>(subAuthorities)...));
582         return result;
583     }
584 
585 #ifdef WIL_ENABLE_EXCEPTIONS
586     template<typename... Ts> bool test_token_membership(_In_opt_ HANDLE token, const SID_IDENTIFIER_AUTHORITY& sidAuthority,
587         Ts&&... subAuthorities)
588     {
589         bool result;
590         THROW_IF_FAILED(test_token_membership_nothrow(&result, token, sidAuthority, wistd::forward<Ts>(subAuthorities)...));
591         return result;
592     }
593 #endif
594 
595 } //namespace wil
596 
597 #endif // __WIL_TOKEN_HELPERS_INCLUDED
598