enterprise_util_mac.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "base/enterprise_util.h"
  5. #import <OpenDirectory/OpenDirectory.h>
  6. #include <string>
  7. #include <vector>
  8. #include "base/logging.h"
  9. #include "base/mac/foundation_util.h"
  10. #include "base/process/launch.h"
  11. #include "base/strings/string_split.h"
  12. #include "base/strings/string_util.h"
  13. #include "base/strings/sys_string_conversions.h"
  14. namespace base {
  15. bool IsManagedDevice() {
  16. // MDM enrollment indicates the device is actively being managed. Simply being
  17. // joined to a domain, however, does not.
  18. // IsDeviceRegisteredWithManagementNew is only available after 10.13.4.
  19. // Eventually switch to it when that is the minimum OS required by Chromium.
  20. if (@available(macOS 10.13.4, *)) {
  21. base::MacDeviceManagementStateNew mdm_state =
  22. base::IsDeviceRegisteredWithManagementNew();
  23. return mdm_state ==
  24. base::MacDeviceManagementStateNew::kLimitedMDMEnrollment ||
  25. mdm_state == base::MacDeviceManagementStateNew::kFullMDMEnrollment ||
  26. mdm_state == base::MacDeviceManagementStateNew::kDEPMDMEnrollment;
  27. }
  28. base::MacDeviceManagementStateOld mdm_state =
  29. base::IsDeviceRegisteredWithManagementOld();
  30. return mdm_state == base::MacDeviceManagementStateOld::kMDMEnrollment;
  31. }
  32. bool IsEnterpriseDevice() {
  33. // Domain join is a basic indicator of being an enterprise device.
  34. DeviceUserDomainJoinState join_state = AreDeviceAndUserJoinedToDomain();
  35. return join_state.device_joined || join_state.user_joined;
  36. }
  37. MacDeviceManagementStateOld IsDeviceRegisteredWithManagementOld() {
  38. static MacDeviceManagementStateOld state = [] {
  39. @autoreleasepool {
  40. std::vector<std::string> profiler_argv{"/usr/sbin/system_profiler",
  41. "SPConfigurationProfileDataType",
  42. "-detailLevel",
  43. "mini",
  44. "-timeout",
  45. "15",
  46. "-xml"};
  47. std::string profiler_stdout;
  48. if (!GetAppOutput(profiler_argv, &profiler_stdout)) {
  49. LOG(WARNING) << "Could not get system_profiler output.";
  50. return MacDeviceManagementStateOld::kFailureAPIUnavailable;
  51. };
  52. NSArray* root = base::mac::ObjCCast<NSArray>([NSPropertyListSerialization
  53. propertyListWithData:[SysUTF8ToNSString(profiler_stdout)
  54. dataUsingEncoding:NSUTF8StringEncoding]
  55. options:NSPropertyListImmutable
  56. format:nil
  57. error:nil]);
  58. if (!root) {
  59. LOG(WARNING) << "Could not parse system_profiler output.";
  60. return MacDeviceManagementStateOld::kFailureUnableToParseResult;
  61. };
  62. for (NSDictionary* results in root) {
  63. for (NSDictionary* dict in results[@"_items"]) {
  64. for (NSDictionary* device_config_profiles in dict[@"_items"]) {
  65. for (NSDictionary* profile_item in
  66. device_config_profiles[@"_items"]) {
  67. if (![profile_item[@"_name"] isEqual:@"com.apple.mdm"])
  68. continue;
  69. NSString* payload_data =
  70. profile_item[@"spconfigprofile_payload_data"];
  71. NSDictionary* payload_data_dict =
  72. base::mac::ObjCCast<NSDictionary>([NSPropertyListSerialization
  73. propertyListWithData:
  74. [payload_data dataUsingEncoding:NSUTF8StringEncoding]
  75. options:NSPropertyListImmutable
  76. format:nil
  77. error:nil]);
  78. if (!payload_data_dict)
  79. continue;
  80. // Verify that the URL validates.
  81. if ([NSURL URLWithString:payload_data_dict[@"CheckInURL"]])
  82. return MacDeviceManagementStateOld::kMDMEnrollment;
  83. }
  84. }
  85. }
  86. }
  87. return MacDeviceManagementStateOld::kNoEnrollment;
  88. }
  89. }();
  90. return state;
  91. }
  92. MacDeviceManagementStateNew IsDeviceRegisteredWithManagementNew() {
  93. static MacDeviceManagementStateNew state = [] {
  94. if (@available(macOS 10.13.4, *)) {
  95. std::vector<std::string> profiles_argv{"/usr/bin/profiles", "status",
  96. "-type", "enrollment"};
  97. std::string profiles_stdout;
  98. if (!GetAppOutput(profiles_argv, &profiles_stdout)) {
  99. LOG(WARNING) << "Could not get profiles output.";
  100. return MacDeviceManagementStateNew::kFailureAPIUnavailable;
  101. }
  102. // Sample output of `profiles` with full MDM enrollment:
  103. // Enrolled via DEP: Yes
  104. // MDM enrollment: Yes (User Approved)
  105. // MDM server: https://applemdm.example.com/some/path?foo=bar
  106. StringPairs property_states;
  107. if (!SplitStringIntoKeyValuePairs(profiles_stdout, ':', '\n',
  108. &property_states)) {
  109. return MacDeviceManagementStateNew::kFailureUnableToParseResult;
  110. }
  111. bool enrolled_via_dep = false;
  112. bool mdm_enrollment_not_approved = false;
  113. bool mdm_enrollment_user_approved = false;
  114. for (const auto& property_state : property_states) {
  115. StringPiece property =
  116. TrimString(property_state.first, kWhitespaceASCII, TRIM_ALL);
  117. StringPiece state =
  118. TrimString(property_state.second, kWhitespaceASCII, TRIM_ALL);
  119. if (property == "Enrolled via DEP") {
  120. if (state == "Yes")
  121. enrolled_via_dep = true;
  122. else if (state != "No")
  123. return MacDeviceManagementStateNew::kFailureUnableToParseResult;
  124. } else if (property == "MDM enrollment") {
  125. if (state == "Yes")
  126. mdm_enrollment_not_approved = true;
  127. else if (state == "Yes (User Approved)")
  128. mdm_enrollment_user_approved = true;
  129. else if (state != "No")
  130. return MacDeviceManagementStateNew::kFailureUnableToParseResult;
  131. } else {
  132. // Ignore any other output lines, for future extensibility.
  133. }
  134. }
  135. if (!enrolled_via_dep && !mdm_enrollment_not_approved &&
  136. !mdm_enrollment_user_approved) {
  137. return MacDeviceManagementStateNew::kNoEnrollment;
  138. }
  139. if (!enrolled_via_dep && mdm_enrollment_not_approved &&
  140. !mdm_enrollment_user_approved) {
  141. return MacDeviceManagementStateNew::kLimitedMDMEnrollment;
  142. }
  143. if (!enrolled_via_dep && !mdm_enrollment_not_approved &&
  144. mdm_enrollment_user_approved) {
  145. return MacDeviceManagementStateNew::kFullMDMEnrollment;
  146. }
  147. if (enrolled_via_dep && !mdm_enrollment_not_approved &&
  148. mdm_enrollment_user_approved) {
  149. return MacDeviceManagementStateNew::kDEPMDMEnrollment;
  150. }
  151. return MacDeviceManagementStateNew::kFailureUnableToParseResult;
  152. } else {
  153. return MacDeviceManagementStateNew::kFailureAPIUnavailable;
  154. }
  155. }();
  156. return state;
  157. }
  158. DeviceUserDomainJoinState AreDeviceAndUserJoinedToDomain() {
  159. static DeviceUserDomainJoinState state = [] {
  160. DeviceUserDomainJoinState state{false, false};
  161. @autoreleasepool {
  162. ODSession* session = [ODSession defaultSession];
  163. if (session == nil) {
  164. DLOG(WARNING) << "ODSession default session is nil.";
  165. return state;
  166. }
  167. NSError* error = nil;
  168. NSArray<NSString*>* all_node_names =
  169. [session nodeNamesAndReturnError:&error];
  170. if (!all_node_names) {
  171. DLOG(WARNING) << "ODSession failed to give node names: "
  172. << error.localizedDescription.UTF8String;
  173. return state;
  174. }
  175. NSUInteger num_nodes = all_node_names.count;
  176. if (num_nodes < 3) {
  177. DLOG(WARNING) << "ODSession returned too few node names: "
  178. << all_node_names.description.UTF8String;
  179. return state;
  180. }
  181. if (num_nodes > 3) {
  182. // Non-enterprise machines have:"/Search", "/Search/Contacts",
  183. // "/Local/Default". Everything else would be enterprise management.
  184. state.device_joined = true;
  185. }
  186. ODNode* node = [ODNode nodeWithSession:session
  187. type:kODNodeTypeAuthentication
  188. error:&error];
  189. if (node == nil) {
  190. DLOG(WARNING) << "ODSession cannot obtain the authentication node: "
  191. << error.localizedDescription.UTF8String;
  192. return state;
  193. }
  194. // Now check the currently logged on user.
  195. ODQuery* query = [ODQuery queryWithNode:node
  196. forRecordTypes:kODRecordTypeUsers
  197. attribute:kODAttributeTypeRecordName
  198. matchType:kODMatchEqualTo
  199. queryValues:NSUserName()
  200. returnAttributes:kODAttributeTypeAllAttributes
  201. maximumResults:0
  202. error:&error];
  203. if (query == nil) {
  204. DLOG(WARNING) << "ODSession cannot create user query: "
  205. << mac::NSToCFCast(error);
  206. return state;
  207. }
  208. NSArray* results = [query resultsAllowingPartial:NO error:&error];
  209. if (!results) {
  210. DLOG(WARNING) << "ODSession cannot obtain current user node: "
  211. << error.localizedDescription.UTF8String;
  212. return state;
  213. }
  214. if (results.count != 1) {
  215. DLOG(WARNING) << @"ODSession unexpected number of user nodes: "
  216. << results.count;
  217. }
  218. for (id element in results) {
  219. ODRecord* record = mac::ObjCCastStrict<ODRecord>(element);
  220. NSArray* attributes =
  221. [record valuesForAttribute:kODAttributeTypeMetaRecordName
  222. error:nil];
  223. for (id attribute in attributes) {
  224. NSString* attribute_value = mac::ObjCCastStrict<NSString>(attribute);
  225. // Example: "uid=johnsmith,ou=People,dc=chromium,dc=org
  226. NSRange domain_controller =
  227. [attribute_value rangeOfString:@"(^|,)\\s*dc="
  228. options:NSRegularExpressionSearch];
  229. if (domain_controller.length > 0) {
  230. state.user_joined = true;
  231. }
  232. }
  233. // Scan alternative identities.
  234. attributes =
  235. [record valuesForAttribute:kODAttributeTypeAltSecurityIdentities
  236. error:nil];
  237. for (id attribute in attributes) {
  238. NSString* attribute_value = mac::ObjCCastStrict<NSString>(attribute);
  239. NSRange icloud =
  240. [attribute_value rangeOfString:@"CN=com.apple.idms.appleid.prd"
  241. options:NSCaseInsensitiveSearch];
  242. if (!icloud.length) {
  243. // Any alternative identity that is not iCloud is likely enterprise
  244. // management.
  245. state.user_joined = true;
  246. }
  247. }
  248. }
  249. }
  250. return state;
  251. }();
  252. return state;
  253. }
  254. } // namespace base