shill: Profile: Add "UserHash" property

Add a read-only property to the Profile that contains an opaque
"UserHash" string.  This string is populated using a new field
in the Profile::Identifier structure, which can be filled in as
it is being created by by using a new Manager::InsertUserProfile
RPC call.  InsertUserProfile is so named because it's still not
completely resolved in the design where in the profile stack a
profile inserted via this method will end up (See design doc
https://go/cros-multiprofile for up-to-date discussion).

CQ-DEPEND=CL:48378
BUG=chromium:231858
TEST=Unit tests

Change-Id: I0c6623f3d7253eb4d773712cddb72a222c0be6c5
Reviewed-on: https://gerrit.chromium.org/gerrit/48405
Commit-Queue: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
diff --git a/profile.cc b/profile.cc
index 6aa0406..7d84243 100644
--- a/profile.cc
+++ b/profile.cc
@@ -47,6 +47,7 @@
   // flimflam::kCheckPortalListProperty: Registered in DefaultProfile
   // flimflam::kCountryProperty: Registered in DefaultProfile
   store_.RegisterConstString(flimflam::kNameProperty, &name_.identifier);
+  store_.RegisterConstString(kUserHashProperty, &name_.user_hash);
 
   // flimflam::kOfflineModeProperty: Registered in DefaultProfile
   // flimflam::kPortalURLProperty: Registered in DefaultProfile
@@ -224,6 +225,7 @@
   return true;
 }
 
+// static
 bool Profile::ParseIdentifier(const string &raw, Identifier *parsed) {
   if (raw.empty()) {
     return false;
@@ -253,6 +255,18 @@
   return true;
 }
 
+// static
+string Profile::IdentifierToString(const Identifier &name) {
+  if (name.user.empty()) {
+    // Format: "identifier".
+    return name.identifier;
+  }
+
+  // Format: "~user/identifier".
+  return base::StringPrintf(
+      "~%s/%s", name.user.c_str(), name.identifier.c_str());
+}
+
 bool Profile::MatchesIdentifier(const Identifier &name) const {
   return name.user == name_.user && name.identifier == name_.identifier;
 }