diff --git a/setup/azurecmdfiles.wxi b/setup/azurecmdfiles.wxi
index 32066b7b8500..395b69c37137 100644
--- a/setup/azurecmdfiles.wxi
+++ b/setup/azurecmdfiles.wxi
@@ -1,6597 +1,6629 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Common/Commands.Common.Authentication/Authentication/AadAuthenticationException.cs b/src/Common/Commands.Common.Authentication/Authentication/AadAuthenticationException.cs
new file mode 100644
index 000000000000..7fc5cf31504f
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/AadAuthenticationException.cs
@@ -0,0 +1,94 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// Base class representing an exception that occurs when
+ /// authenticating against Azure Active Directory
+ ///
+ [Serializable]
+ public abstract class AadAuthenticationException : Exception
+ {
+ protected AadAuthenticationException()
+ {
+ }
+
+ protected AadAuthenticationException(string message) : base(message)
+ {
+ }
+
+ protected AadAuthenticationException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+
+ ///
+ /// Exception that gets thrown when the user explicitly
+ /// cancels an authentication operation.
+ ///
+ [Serializable]
+ public class AadAuthenticationCanceledException : AadAuthenticationException
+ {
+ public AadAuthenticationCanceledException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+
+ ///
+ /// Exception that gets thrown when the ADAL library
+ /// is unable to authenticate without a popup dialog.
+ ///
+ [Serializable]
+ public class AadAuthenticationFailedWithoutPopupException : AadAuthenticationException
+ {
+ public AadAuthenticationFailedWithoutPopupException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+
+ ///
+ /// Exception that gets thrown if an authentication operation
+ /// fails on the server.
+ ///
+ [Serializable]
+ public class AadAuthenticationFailedException : AadAuthenticationException
+ {
+ public AadAuthenticationFailedException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+
+ ///
+ /// Exception thrown if a refresh token has expired.
+ ///
+ [Serializable]
+ public class AadAuthenticationCantRenewException : AadAuthenticationException
+ {
+ public AadAuthenticationCantRenewException()
+ {
+ }
+
+ public AadAuthenticationCantRenewException(string message) : base(message)
+ {
+ }
+
+ public AadAuthenticationCantRenewException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Common/Commands.Common.Authentication/Authentication/AccessTokenCredential.cs b/src/Common/Commands.Common.Authentication/Authentication/AccessTokenCredential.cs
new file mode 100644
index 000000000000..8d4b20a3eb1c
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/AccessTokenCredential.cs
@@ -0,0 +1,50 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public class AccessTokenCredential : SubscriptionCloudCredentials
+ {
+ private readonly Guid subscriptionId;
+ private readonly IAccessToken token;
+
+ public AccessTokenCredential(Guid subscriptionId, IAccessToken token)
+ {
+ this.subscriptionId = subscriptionId;
+ this.token = token;
+ this.TenantID = token.TenantId;
+ }
+
+ public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ token.AuthorizeRequest((tokenType, tokenValue) => {
+ request.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue);
+ });
+ return base.ProcessHttpRequestAsync(request, cancellationToken);
+ }
+
+ public override string SubscriptionId
+ {
+ get { return subscriptionId.ToString(); }
+ }
+
+ public string TenantID { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Common/Commands.Common.Authentication/Authentication/AdalConfiguration.cs b/src/Common/Commands.Common.Authentication/Authentication/AdalConfiguration.cs
new file mode 100644
index 000000000000..fab3702cd45c
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/AdalConfiguration.cs
@@ -0,0 +1,63 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using System;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// Class storing the configuration information needed
+ /// for ADAL to request token from the right AD tenant
+ /// depending on environment.
+ ///
+ public class AdalConfiguration
+ {
+ //
+ // These constants define the default values to use for AD authentication
+ // against RDFE
+ //
+ public const string PowerShellClientId = "1950a258-227b-4e31-a9cf-717495945fc2";
+
+ public static readonly Uri PowerShellRedirectUri = new Uri("urn:ietf:wg:oauth:2.0:oob");
+
+ // ID for site to pass to enable EBD (email-based differentiation)
+ // This gets passed in the call to get the azure branding on the
+ // login window. Also adding popup flag to handle overly large login windows.
+ public const string EnableEbdMagicCookie = "site_id=501358&display=popup";
+
+ public string AdEndpoint { get;set; }
+
+ public bool ValidateAuthority { get; set; }
+
+ public string AdDomain { get; set; }
+
+ public string ClientId { get; set; }
+
+ public Uri ClientRedirectUri { get; set; }
+
+ public string ResourceClientUri { get; set; }
+
+ public TokenCache TokenCache { get; set; }
+
+ public AdalConfiguration()
+ {
+ ClientId = PowerShellClientId;
+ ClientRedirectUri = PowerShellRedirectUri;
+ ValidateAuthority = true;
+ AdEndpoint = string.Empty;
+ ResourceClientUri = "https://management.core.windows.net/";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Common/Commands.Common.Authentication/Authentication/AdalTokenProvider.cs b/src/Common/Commands.Common.Authentication/Authentication/AdalTokenProvider.cs
new file mode 100644
index 000000000000..b8bb95f780f7
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/AdalTokenProvider.cs
@@ -0,0 +1,68 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using System;
+using System.Security;
+using System.Windows.Forms;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// A token provider that uses ADAL to retrieve
+ /// tokens from Azure Active Directory
+ ///
+ public class AdalTokenProvider : ITokenProvider
+ {
+ private readonly ITokenProvider userTokenProvider;
+ private readonly ITokenProvider servicePrincipalTokenProvider;
+
+ public AdalTokenProvider()
+ : this(new ConsoleParentWindow())
+ {
+ }
+
+ public AdalTokenProvider(IWin32Window parentWindow)
+ {
+ this.userTokenProvider = new UserTokenProvider(parentWindow);
+ servicePrincipalTokenProvider = new ServicePrincipalTokenProvider();
+ }
+
+ public IAccessToken GetAccessToken(AdalConfiguration config, ShowDialog promptBehavior, string userId, SecureString password,
+ AzureAccount.AccountType credentialType)
+ {
+ switch (credentialType)
+ {
+ case AzureAccount.AccountType.User:
+ return userTokenProvider.GetAccessToken(config, promptBehavior, userId, password, credentialType);
+ case AzureAccount.AccountType.ServicePrincipal:
+ return servicePrincipalTokenProvider.GetAccessToken(config, promptBehavior, userId, password, credentialType);
+ default:
+ throw new ArgumentException(Resources.UnknownCredentialType, "credentialType");
+ }
+ }
+
+ public IAccessToken GetAccessTokenWithCertificate(AdalConfiguration config, string clientId, string certificate, AzureAccount.AccountType credentialType)
+ {
+ switch (credentialType)
+ {
+ case AzureAccount.AccountType.ServicePrincipal:
+ return servicePrincipalTokenProvider.GetAccessTokenWithCertificate(config, clientId, certificate, credentialType);
+ default:
+ throw new ArgumentException(string.Format(Resources.UnsupportedCredentialType, credentialType), "credentialType");
+ }
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/CertificateApplicationCredentialProvider.cs b/src/Common/Commands.Common.Authentication/Authentication/CertificateApplicationCredentialProvider.cs
new file mode 100644
index 000000000000..0de1ad9d82e2
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/CertificateApplicationCredentialProvider.cs
@@ -0,0 +1,60 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using Microsoft.Rest.Azure.Authentication;
+using System.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// Interface to the certificate store for authentication
+ ///
+ internal sealed class CertificateApplicationCredentialProvider : IApplicationAuthenticationProvider
+ {
+ private string _certificateThumbprint;
+
+ ///
+ /// Create a certificate provider
+ ///
+ ///
+ public CertificateApplicationCredentialProvider(string certificateThumbprint)
+ {
+ this._certificateThumbprint = certificateThumbprint;
+ }
+
+ ///
+ /// Authenticate using certificate thumbprint from the datastore
+ ///
+ /// The active directory client id for the application.
+ /// The intended audience for authentication
+ /// The AD AuthenticationContext to use
+ ///
+ public async Task AuthenticateAsync(string clientId, string audience, AuthenticationContext context)
+ {
+ var task = new Task(() =>
+ {
+ return AzureSession.DataStore.GetCertificate(this._certificateThumbprint);
+ });
+ task.Start();
+ var certificate = await task.ConfigureAwait(false);
+
+ return await context.AcquireTokenAsync(
+ audience,
+ new ClientAssertionCertificate(clientId, certificate));
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/ConsoleParentWindow.cs b/src/Common/Commands.Common.Authentication/Authentication/ConsoleParentWindow.cs
new file mode 100644
index 000000000000..6ee81dca2270
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/ConsoleParentWindow.cs
@@ -0,0 +1,35 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// An implementation of that gives the
+ /// windows handle for the current console window.
+ ///
+ public class ConsoleParentWindow : IWin32Window
+ {
+ public IntPtr Handle { get { return NativeMethods.GetConsoleWindow(); } }
+
+ static class NativeMethods
+ {
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr GetConsoleWindow();
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/CredStore.cs b/src/Common/Commands.Common.Authentication/Authentication/CredStore.cs
new file mode 100644
index 000000000000..ca23ed3e6a2e
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/CredStore.cs
@@ -0,0 +1,114 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// Class wrapping PInvoke signatures for Windows Credential store
+ ///
+ internal static class CredStore
+ {
+ internal enum CredentialType
+ {
+ Generic = 1,
+ }
+
+ internal static class NativeMethods
+ {
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ internal extern static bool CredRead(
+ string targetName,
+ CredentialType type,
+ int flags,
+ [Out] out IntPtr pCredential
+ );
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ internal extern static bool CredEnumerate(
+ string targetName,
+ int flags,
+ [Out] out int count,
+ [Out] out IntPtr pCredential
+ );
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ internal extern static bool CredDelete(
+ string targetName,
+ CredentialType type,
+ int flags
+ );
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ internal extern static bool CredWrite(
+ IntPtr pCredential,
+ int flags
+ );
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ internal extern static bool CredFree(
+ IntPtr pCredential
+ );
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable", Justification = "Wrapper for native struct")]
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct Credential
+ {
+ public Credential(string userName, string key, string value)
+ {
+ this.flags = 0;
+ this.type = CredentialType.Generic;
+
+ // set the key in the targetName
+ this.targetName = key;
+
+ this.targetAlias = null;
+ this.comment = null;
+ this.lastWritten.dwHighDateTime = 0;
+ this.lastWritten.dwLowDateTime = 0;
+
+ // set the value in credentialBlob.
+ this.credentialBlob = Marshal.StringToHGlobalUni(value);
+ this.credentialBlobSize = (uint)((value.Length + 1) * 2);
+
+ this.persist = 1;
+ this.attibuteCount = 0;
+ this.attributes = IntPtr.Zero;
+ this.userName = userName;
+ }
+
+ internal uint flags;
+ internal CredentialType type;
+ internal string targetName;
+ internal string comment;
+ internal System.Runtime.InteropServices.ComTypes.FILETIME lastWritten;
+ internal uint credentialBlobSize;
+ internal IntPtr credentialBlob;
+ internal uint persist;
+ internal uint attibuteCount;
+ internal IntPtr attributes;
+ internal string targetAlias;
+ internal string userName;
+ }
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/IAccessToken.cs b/src/Common/Commands.Common.Authentication/Authentication/IAccessToken.cs
new file mode 100644
index 000000000000..c295a151a318
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/IAccessToken.cs
@@ -0,0 +1,31 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public interface IAccessToken
+ {
+ void AuthorizeRequest(Action authTokenSetter);
+
+ string AccessToken { get; }
+
+ string UserId { get; }
+
+ string TenantId { get; }
+
+ LoginType LoginType { get; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/ITokenProvider.cs b/src/Common/Commands.Common.Authentication/Authentication/ITokenProvider.cs
new file mode 100644
index 000000000000..5819708309a1
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/ITokenProvider.cs
@@ -0,0 +1,50 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using System.Security;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// This interface represents objects that can be used
+ /// to obtain and manage access tokens.
+ ///
+ public interface ITokenProvider
+ {
+ ///
+ /// Get a new login token for the given environment, user credential,
+ /// and credential type.
+ ///
+ /// Configuration.
+ /// Prompt behavior.
+ /// User ID/Service principal to get the token for.
+ /// Secure strings with password/service principal key.
+ /// Credential type.
+ /// An access token.
+ IAccessToken GetAccessToken(AdalConfiguration config, ShowDialog promptBehavior, string userId,
+ SecureString password, AzureAccount.AccountType credentialType);
+
+ ///
+ /// Get a new authentication token for the given environment
+ ///
+ /// The ADAL Configuration
+ /// The id for the given principal
+ /// The certificate thumbprint for this user
+ /// The account type
+ /// An access token, which can be renewed
+ IAccessToken GetAccessTokenWithCertificate(AdalConfiguration config, string principalId, string certificateThumbprint,
+ AzureAccount.AccountType credentialType);
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/KeyStoreApplicationCredentialProvider.cs b/src/Common/Commands.Common.Authentication/Authentication/KeyStoreApplicationCredentialProvider.cs
new file mode 100644
index 000000000000..bf9a55c6e3a4
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/KeyStoreApplicationCredentialProvider.cs
@@ -0,0 +1,57 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using Microsoft.Rest.Azure.Authentication;
+using System.Security;
+using System.Threading.Tasks;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// Interface to the keystore for authentication
+ ///
+ internal sealed class KeyStoreApplicationCredentialProvider : IApplicationAuthenticationProvider
+ {
+ private string _tenantId;
+
+ ///
+ /// Create a credential provider
+ ///
+ ///
+ public KeyStoreApplicationCredentialProvider(string tenant)
+ {
+ this._tenantId = tenant;
+ }
+
+ ///
+ /// Authenticate using the secret for the specified client from the key store
+ ///
+ /// The active directory client id for the application.
+ /// The intended audience for authentication
+ /// The AD AuthenticationContext to use
+ ///
+ public async Task AuthenticateAsync(string clientId, string audience, AuthenticationContext context)
+ {
+ var task = new Task(() =>
+ {
+ return ServicePrincipalKeyStore.GetKey(clientId, _tenantId);
+ });
+ task.Start();
+ var key = await task.ConfigureAwait(false);
+
+ return await context.AcquireTokenAsync(audience, new ClientCredential(clientId, key));
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/LoginType.cs b/src/Common/Commands.Common.Authentication/Authentication/LoginType.cs
new file mode 100644
index 000000000000..16c96a77657f
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/LoginType.cs
@@ -0,0 +1,29 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public enum LoginType
+ {
+ ///
+ /// User is logging in with orgid credentials
+ ///
+ OrgId,
+
+ ///
+ /// User is logging in with liveid credentials
+ ///
+ LiveId
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/ProtectedFileTokenCache.cs b/src/Common/Commands.Common.Authentication/Authentication/ProtectedFileTokenCache.cs
new file mode 100644
index 000000000000..5846aa750614
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/ProtectedFileTokenCache.cs
@@ -0,0 +1,121 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// An implementation of the Adal token cache that stores the cache items
+ /// in the DPAPI-protected file.
+ ///
+ public class ProtectedFileTokenCache : TokenCache
+ {
+ private static readonly string CacheFileName = Path.Combine(AzureSession.ProfileDirectory, AzureSession.TokenCacheFile);
+
+ private static readonly object fileLock = new object();
+
+ private static readonly Lazy instance = new Lazy(() => new ProtectedFileTokenCache());
+
+ // Initializes the cache against a local file.
+ // If the file is already present, it loads its content in the ADAL cache
+ private ProtectedFileTokenCache()
+ {
+ Initialize(CacheFileName);
+ }
+
+ private void Initialize(string fileName)
+ {
+ AfterAccess = AfterAccessNotification;
+ BeforeAccess = BeforeAccessNotification;
+ lock (fileLock)
+ {
+ if (AzureSession.DataStore.FileExists(fileName))
+ {
+ var existingData = AzureSession.DataStore.ReadFileAsBytes(fileName);
+ if (existingData != null)
+ {
+ try
+ {
+ Deserialize(ProtectedData.Unprotect(existingData, null, DataProtectionScope.CurrentUser));
+ }
+ catch (CryptographicException)
+ {
+ AzureSession.DataStore.DeleteFile(fileName);
+ }
+ }
+ }
+ }
+ }
+
+ public ProtectedFileTokenCache(string cacheFile)
+ {
+ Initialize(cacheFile);
+ }
+
+ // Empties the persistent store.
+ public override void Clear()
+ {
+ base.Clear();
+ if (AzureSession.DataStore.FileExists(CacheFileName))
+ {
+ AzureSession.DataStore.DeleteFile(CacheFileName);
+ }
+ }
+
+ // Triggered right before ADAL needs to access the cache.
+ // Reload the cache from the persistent store in case it changed since the last access.
+ void BeforeAccessNotification(TokenCacheNotificationArgs args)
+ {
+ lock (fileLock)
+ {
+ if (AzureSession.DataStore.FileExists(CacheFileName))
+ {
+ var existingData = AzureSession.DataStore.ReadFileAsBytes(CacheFileName);
+ if (existingData != null)
+ {
+ try
+ {
+ Deserialize(ProtectedData.Unprotect(existingData, null, DataProtectionScope.CurrentUser));
+ }
+ catch (CryptographicException)
+ {
+ AzureSession.DataStore.DeleteFile(CacheFileName);
+ }
+ }
+ }
+ }
+ }
+
+ // Triggered right after ADAL accessed the cache.
+ void AfterAccessNotification(TokenCacheNotificationArgs args)
+ {
+ // if the access operation resulted in a cache update
+ if (HasStateChanged)
+ {
+ lock (fileLock)
+ {
+ // reflect changes in the persistent store
+ AzureSession.DataStore.WriteFile(CacheFileName,
+ ProtectedData.Protect(Serialize(), null, DataProtectionScope.CurrentUser));
+ // once the write operation took place, restore the HasStateChanged bit to false
+ HasStateChanged = false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/ServicePrincipalKeyStore.cs b/src/Common/Commands.Common.Authentication/Authentication/ServicePrincipalKeyStore.cs
new file mode 100644
index 000000000000..4229221d763c
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/ServicePrincipalKeyStore.cs
@@ -0,0 +1,117 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// Helper class to store service principal keys and retrieve them
+ /// from the Windows Credential Store.
+ ///
+ public static class ServicePrincipalKeyStore
+ {
+ private const string keyStoreUserName = "PowerShellServicePrincipalKey";
+ private const string targetNamePrefix = "AzureSession:target=";
+
+ public static void SaveKey(string appId, string tenantId, SecureString serviceKey)
+ {
+ var credential = new CredStore.NativeMethods.Credential
+ {
+ flags = 0,
+ type = CredStore.CredentialType.Generic,
+ targetName = CreateKey(appId, tenantId),
+ targetAlias = null,
+ comment = null,
+ lastWritten = new FILETIME {dwHighDateTime = 0, dwLowDateTime = 0},
+ persist = 2, // persist on local machine
+ attibuteCount = 0,
+ attributes = IntPtr.Zero,
+ userName = keyStoreUserName
+ };
+
+ // Pull bits out of SecureString to put in credential
+ IntPtr credPtr = IntPtr.Zero;
+ try
+ {
+ credential.credentialBlob = Marshal.SecureStringToGlobalAllocUnicode(serviceKey);
+ credential.credentialBlobSize = (uint)(serviceKey.Length * Marshal.SystemDefaultCharSize);
+
+ int size = Marshal.SizeOf(credential);
+ credPtr = Marshal.AllocHGlobal(size);
+
+ Marshal.StructureToPtr(credential, credPtr, false);
+ CredStore.NativeMethods.CredWrite(credPtr, 0);
+ }
+ finally
+ {
+ if (credPtr != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(credPtr);
+ }
+
+ Marshal.ZeroFreeGlobalAllocUnicode(credential.credentialBlob);
+ }
+ }
+
+ public static SecureString GetKey(string appId, string tenantId)
+ {
+ IntPtr pCredential = IntPtr.Zero;
+ try
+ {
+ if (CredStore.NativeMethods.CredRead(
+ CreateKey(appId, tenantId),
+ CredStore.CredentialType.Generic, 0,
+ out pCredential))
+ {
+ var credential = (CredStore.NativeMethods.Credential)
+ Marshal.PtrToStructure(pCredential, typeof (CredStore.NativeMethods.Credential));
+ unsafe
+ {
+ return new SecureString((char*) (credential.credentialBlob),
+ (int)(credential.credentialBlobSize/Marshal.SystemDefaultCharSize));
+ }
+ }
+ return null;
+ }
+ catch
+ {
+ // we could be running in an environment that does not have credentials store
+ }
+ finally
+ {
+ if (pCredential != IntPtr.Zero)
+ {
+ CredStore.NativeMethods.CredFree(pCredential);
+ }
+ }
+
+ return null;
+ }
+
+
+ public static void DeleteKey(string appId, string tenantId)
+ {
+ CredStore.NativeMethods.CredDelete(CreateKey(appId, tenantId), CredStore.CredentialType.Generic, 0);
+ }
+
+ private static string CreateKey(string appId, string tenantId)
+ {
+ return string.Format("{0}AppId={1};Tenant={2}", targetNamePrefix, appId, tenantId);
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Authentication/ServicePrincipalTokenProvider.cs b/src/Common/Commands.Common.Authentication/Authentication/ServicePrincipalTokenProvider.cs
new file mode 100644
index 000000000000..bd1a6c8c6e01
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/ServicePrincipalTokenProvider.cs
@@ -0,0 +1,166 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Hyak.Common;
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using System;
+using System.Collections.Generic;
+using System.Security;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ internal class ServicePrincipalTokenProvider : ITokenProvider
+ {
+ private static readonly TimeSpan expirationThreshold = TimeSpan.FromMinutes(5);
+
+ public IAccessToken GetAccessToken(AdalConfiguration config, ShowDialog promptBehavior, string userId, SecureString password,
+ AzureAccount.AccountType credentialType)
+ {
+ if (credentialType == AzureAccount.AccountType.User)
+ {
+ throw new ArgumentException(string.Format(Resources.InvalidCredentialType, "User"), "credentialType");
+ }
+ return new ServicePrincipalAccessToken(config, AcquireTokenWithSecret(config, userId, password), this.RenewWithSecret, userId);
+ }
+
+ public IAccessToken GetAccessTokenWithCertificate(AdalConfiguration config, string clientId, string certificateThumbprint, AzureAccount.AccountType credentialType)
+ {
+ if (credentialType == AzureAccount.AccountType.User)
+ {
+ throw new ArgumentException(string.Format(Resources.InvalidCredentialType, "User"), "credentialType");
+ }
+ return new ServicePrincipalAccessToken(config, AcquireTokenWithCertificate(config, clientId, certificateThumbprint),
+ (adalConfig, appId) => this.RenewWithCertificate(adalConfig, appId, certificateThumbprint), clientId);
+ }
+
+ private AuthenticationContext GetContext(AdalConfiguration config)
+ {
+ string authority = config.AdEndpoint + config.AdDomain;
+ return new AuthenticationContext(authority, config.ValidateAuthority, config.TokenCache);
+ }
+
+ private AuthenticationResult AcquireTokenWithSecret(AdalConfiguration config, string appId, SecureString appKey)
+ {
+ if (appKey == null)
+ {
+ return RenewWithSecret(config, appId);
+ }
+
+ StoreAppKey(appId, config.AdDomain, appKey);
+ var credential = new ClientCredential(appId, appKey);
+ var context = GetContext(config);
+ return context.AcquireToken(config.ResourceClientUri, credential);
+ }
+
+ private AuthenticationResult AcquireTokenWithCertificate(AdalConfiguration config, string appId,
+ string thumbprint)
+ {
+ var certificate = AzureSession.DataStore.GetCertificate(thumbprint);
+ if (certificate == null)
+ {
+ throw new ArgumentException(string.Format(Resources.CertificateNotFoundInStore, thumbprint));
+ }
+
+ var context = GetContext(config);
+ return context.AcquireToken(config.ResourceClientUri, new ClientAssertionCertificate(appId, certificate));
+ }
+
+ private AuthenticationResult RenewWithSecret(AdalConfiguration config, string appId)
+ {
+ TracingAdapter.Information(Resources.SPNRenewTokenTrace, appId, config.AdDomain, config.AdEndpoint,
+ config.ClientId, config.ClientRedirectUri);
+ using (SecureString appKey = LoadAppKey(appId, config.AdDomain))
+ {
+ if (appKey == null)
+ {
+ throw new KeyNotFoundException(string.Format(Resources.ServiceKeyNotFound, appId));
+ }
+ return AcquireTokenWithSecret(config, appId, appKey);
+ }
+ }
+
+ private AuthenticationResult RenewWithCertificate(AdalConfiguration config, string appId,
+ string thumbprint)
+ {
+ TracingAdapter.Information(Resources.SPNRenewTokenTrace, appId, config.AdDomain, config.AdEndpoint,
+ config.ClientId, config.ClientRedirectUri);
+ return AcquireTokenWithCertificate(config, appId, thumbprint);
+ }
+
+ private SecureString LoadAppKey(string appId, string tenantId)
+ {
+ return ServicePrincipalKeyStore.GetKey(appId, tenantId);
+ }
+
+ private void StoreAppKey(string appId, string tenantId, SecureString appKey)
+ {
+ ServicePrincipalKeyStore.SaveKey(appId, tenantId, appKey);
+ }
+
+
+ private class ServicePrincipalAccessToken : IAccessToken
+ {
+ internal readonly AdalConfiguration Configuration;
+ internal AuthenticationResult AuthResult;
+ private readonly Func tokenRenewer;
+ private readonly string appId;
+
+ public ServicePrincipalAccessToken(AdalConfiguration configuration, AuthenticationResult authResult, Func tokenRenewer, string appId)
+ {
+ Configuration = configuration;
+ AuthResult = authResult;
+ this.tokenRenewer = tokenRenewer;
+ this.appId = appId;
+ }
+
+ public void AuthorizeRequest(Action authTokenSetter)
+ {
+ if (IsExpired)
+ {
+ AuthResult = tokenRenewer(Configuration, appId);
+ }
+
+ authTokenSetter(AuthResult.AccessTokenType, AuthResult.AccessToken);
+ }
+
+ public string UserId { get { return appId; } }
+ public string AccessToken { get { return AuthResult.AccessToken; } }
+ public LoginType LoginType { get { return LoginType.OrgId; } }
+ public string TenantId { get { return this.Configuration.AdDomain; } }
+
+ private bool IsExpired
+ {
+ get
+ {
+#if DEBUG
+ if (Environment.GetEnvironmentVariable("FORCE_EXPIRED_ACCESS_TOKEN") != null)
+ {
+ return true;
+ }
+#endif
+
+ var expiration = AuthResult.ExpiresOn;
+ var currentTime = DateTimeOffset.UtcNow;
+ var timeUntilExpiration = expiration - currentTime;
+ TracingAdapter.Information(Resources.SPNTokenExpirationCheckTrace, expiration, currentTime,
+ expirationThreshold, timeUntilExpiration);
+ return timeUntilExpiration < expirationThreshold;
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/Common/Commands.Common.Authentication/Authentication/ShowDialog.cs b/src/Common/Commands.Common.Authentication/Authentication/ShowDialog.cs
new file mode 100644
index 000000000000..3ca6ac512309
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/ShowDialog.cs
@@ -0,0 +1,23 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public enum ShowDialog
+ {
+ Auto,
+ Always,
+ Never
+ }
+}
\ No newline at end of file
diff --git a/src/Common/Commands.Common.Authentication/Authentication/UserTokenProvider.cs b/src/Common/Commands.Common.Authentication/Authentication/UserTokenProvider.cs
new file mode 100644
index 000000000000..54aa45bc5564
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Authentication/UserTokenProvider.cs
@@ -0,0 +1,301 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Hyak.Common;
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Authentication;
+using System.Threading;
+using System.Windows.Forms;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// A token provider that uses ADAL to retrieve
+ /// tokens from Azure Active Directory for user
+ /// credentials.
+ ///
+ internal class UserTokenProvider : ITokenProvider
+ {
+ private readonly IWin32Window parentWindow;
+
+ public UserTokenProvider(IWin32Window parentWindow)
+ {
+ this.parentWindow = parentWindow;
+ }
+
+ public IAccessToken GetAccessToken(AdalConfiguration config, ShowDialog promptBehavior, string userId, SecureString password,
+ AzureAccount.AccountType credentialType)
+ {
+ if (credentialType != AzureAccount.AccountType.User)
+ {
+ throw new ArgumentException(string.Format(Resources.InvalidCredentialType, "User"), "credentialType");
+ }
+
+ return new AdalAccessToken(AcquireToken(config, promptBehavior, userId, password), this, config);
+ }
+
+ private readonly static TimeSpan expirationThreshold = TimeSpan.FromMinutes(5);
+
+ private bool IsExpired(AdalAccessToken token)
+ {
+#if DEBUG
+ if (Environment.GetEnvironmentVariable("FORCE_EXPIRED_ACCESS_TOKEN") != null)
+ {
+ return true;
+ }
+#endif
+ var expiration = token.AuthResult.ExpiresOn;
+ var currentTime = DateTimeOffset.UtcNow;
+ var timeUntilExpiration = expiration - currentTime;
+ TracingAdapter.Information(Resources.UPNTokenExpirationCheckTrace, expiration, currentTime, expirationThreshold,
+ timeUntilExpiration);
+ return timeUntilExpiration < expirationThreshold;
+ }
+
+ private void Renew(AdalAccessToken token)
+ {
+ TracingAdapter.Information(Resources.UPNRenewTokenTrace, token.AuthResult.AccessTokenType, token.AuthResult.ExpiresOn,
+ token.AuthResult.IsMultipleResourceRefreshToken, token.AuthResult.TenantId, token.UserId);
+ var user = token.AuthResult.UserInfo;
+ if (user != null)
+ {
+ TracingAdapter.Information(Resources.UPNRenewTokenUserInfoTrace, user.DisplayableId, user.FamilyName,
+ user.GivenName, user.IdentityProvider, user.UniqueId);
+ }
+ if (IsExpired(token))
+ {
+ TracingAdapter.Information(Resources.UPNExpiredTokenTrace);
+ AuthenticationResult result = AcquireToken(token.Configuration, ShowDialog.Never, token.UserId, null);
+
+ if (result == null)
+ {
+ throw new AuthenticationException(Resources.ExpiredRefreshToken);
+ }
+ else
+ {
+ token.AuthResult = result;
+ }
+ }
+ }
+
+ private AuthenticationContext CreateContext(AdalConfiguration config)
+ {
+ return new AuthenticationContext(config.AdEndpoint + config.AdDomain, config.ValidateAuthority, config.TokenCache)
+ {
+ OwnerWindow = parentWindow
+ };
+ }
+
+ // We have to run this in a separate thread to guarantee that it's STA. This method
+ // handles the threading details.
+ private AuthenticationResult AcquireToken(AdalConfiguration config, ShowDialog promptBehavior, string userId,
+ SecureString password)
+ {
+ AuthenticationResult result = null;
+ Exception ex = null;
+ if (promptBehavior == ShowDialog.Never)
+ {
+ result = SafeAquireToken(config, promptBehavior, userId, password, out ex);
+ }
+ else
+ {
+ var thread = new Thread(() =>
+ {
+ result = SafeAquireToken(config, promptBehavior, userId, password, out ex);
+ });
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Name = "AcquireTokenThread";
+ thread.Start();
+ thread.Join();
+ }
+
+ if (ex != null)
+ {
+ var adex = ex as AdalException;
+ if (adex != null)
+ {
+ if (adex.ErrorCode == AdalError.AuthenticationCanceled)
+ {
+ throw new AadAuthenticationCanceledException(adex.Message, adex);
+ }
+ }
+ if (ex is AadAuthenticationException)
+ {
+ throw ex;
+ }
+ throw new AadAuthenticationFailedException(GetExceptionMessage(ex), ex);
+ }
+
+ return result;
+ }
+
+ private AuthenticationResult SafeAquireToken(
+ AdalConfiguration config,
+ ShowDialog showDialog,
+ string userId,
+ SecureString password,
+ out Exception ex)
+ {
+ try
+ {
+ ex = null;
+ var promptBehavior = (PromptBehavior)Enum.Parse(typeof(PromptBehavior), showDialog.ToString());
+
+ return DoAcquireToken(config, promptBehavior, userId, password);
+ }
+ catch (AdalException adalEx)
+ {
+ if (adalEx.ErrorCode == AdalError.UserInteractionRequired ||
+ adalEx.ErrorCode == AdalError.MultipleTokensMatched)
+ {
+ string message = Resources.AdalUserInteractionRequired;
+ if (adalEx.ErrorCode == AdalError.MultipleTokensMatched)
+ {
+ message = Resources.AdalMultipleTokens;
+ }
+
+ ex = new AadAuthenticationFailedWithoutPopupException(message, adalEx);
+ }
+ else if (adalEx.ErrorCode == AdalError.MissingFederationMetadataUrl)
+ {
+ ex = new AadAuthenticationFailedException(Resources.CredentialOrganizationIdMessage, adalEx);
+ }
+ else
+ {
+ ex = adalEx;
+ }
+ }
+ catch (Exception threadEx)
+ {
+ ex = threadEx;
+ }
+ return null;
+ }
+
+ private AuthenticationResult DoAcquireToken(AdalConfiguration config, PromptBehavior promptBehavior, string userId,
+ SecureString password)
+ {
+ AuthenticationResult result;
+ var context = CreateContext(config);
+
+ TracingAdapter.Information(Resources.UPNAcquireTokenContextTrace, context.Authority, context.CorrelationId,
+ context.ValidateAuthority);
+ TracingAdapter.Information(Resources.UPNAcquireTokenConfigTrace, config.AdDomain, config.AdEndpoint,
+ config.ClientId, config.ClientRedirectUri);
+ if (string.IsNullOrEmpty(userId))
+ {
+ if (promptBehavior != PromptBehavior.Never)
+ {
+ ClearCookies();
+ }
+
+ result = context.AcquireToken(config.ResourceClientUri, config.ClientId,
+ config.ClientRedirectUri, promptBehavior,
+ UserIdentifier.AnyUser, AdalConfiguration.EnableEbdMagicCookie);
+ }
+ else
+ {
+ if (password == null)
+ {
+ result = context.AcquireToken(config.ResourceClientUri, config.ClientId,
+ config.ClientRedirectUri, promptBehavior,
+ new UserIdentifier(userId, UserIdentifierType.RequiredDisplayableId),
+ AdalConfiguration.EnableEbdMagicCookie);
+ }
+ else
+ {
+ UserCredential credential = new UserCredential(userId, password);
+ result = context.AcquireToken(config.ResourceClientUri, config.ClientId, credential);
+ }
+ }
+ return result;
+ }
+
+ private string GetExceptionMessage(Exception ex)
+ {
+ string message = ex.Message;
+ if (ex.InnerException != null)
+ {
+ message += ": " + ex.InnerException.Message;
+ }
+ return message;
+ }
+ ///
+ /// Implementation of using data from ADAL
+ ///
+ private class AdalAccessToken : IAccessToken
+ {
+ internal readonly AdalConfiguration Configuration;
+ internal AuthenticationResult AuthResult;
+ private readonly UserTokenProvider tokenProvider;
+
+ public AdalAccessToken(AuthenticationResult authResult, UserTokenProvider tokenProvider, AdalConfiguration configuration)
+ {
+ AuthResult = authResult;
+ this.tokenProvider = tokenProvider;
+ Configuration = configuration;
+ }
+
+ public void AuthorizeRequest(Action authTokenSetter)
+ {
+ tokenProvider.Renew(this);
+ authTokenSetter(AuthResult.AccessTokenType, AuthResult.AccessToken);
+ }
+
+ public string AccessToken { get { return AuthResult.AccessToken; } }
+ public string UserId { get { return AuthResult.UserInfo.DisplayableId; } }
+
+ public string TenantId { get { return AuthResult.TenantId; } }
+
+ public LoginType LoginType
+ {
+ get
+ {
+ if (AuthResult.UserInfo.IdentityProvider != null)
+ {
+ return LoginType.LiveId;
+ }
+ return LoginType.OrgId;
+ }
+ }
+ }
+
+
+ private void ClearCookies()
+ {
+ NativeMethods.InternetSetOption(IntPtr.Zero, NativeMethods.INTERNET_OPTION_END_BROWSER_SESSION, IntPtr.Zero, 0);
+ }
+
+ private static class NativeMethods
+ {
+ internal const int INTERNET_OPTION_END_BROWSER_SESSION = 42;
+
+ [DllImport("wininet.dll", SetLastError = true)]
+ internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer,
+ int lpdwBufferLength);
+ }
+
+ public IAccessToken GetAccessTokenWithCertificate(AdalConfiguration config, string clientId, string certificate, AzureAccount.AccountType credentialType)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
+
diff --git a/src/Common/Commands.Common.Authentication/AzureSession.cs b/src/Common/Commands.Common.Authentication/AzureSession.cs
new file mode 100644
index 000000000000..51108e9c628c
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/AzureSession.cs
@@ -0,0 +1,89 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Factories;
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using System;
+using System.IO;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// Represents current Azure session.
+ ///
+ public static class AzureSession
+ {
+ ///
+ /// Gets or sets Azure client factory.
+ ///
+ public static IClientFactory ClientFactory { get; set; }
+
+ ///
+ /// Gets or sets Azure authentication factory.
+ ///
+ public static IAuthenticationFactory AuthenticationFactory { get; set; }
+
+ ///
+ /// Gets or sets data persistence store.
+ ///
+ public static IDataStore DataStore { get; set; }
+
+ ///
+ /// Gets or sets the token cache store.
+ ///
+ public static TokenCache TokenCache { get; set; }
+
+ ///
+ /// Gets or sets profile directory.
+ ///
+ public static string ProfileDirectory { get; set; }
+
+ ///
+ /// Gets or sets token cache file path.
+ ///
+ public static string TokenCacheFile { get; set; }
+
+ ///
+ /// Gets or sets profile file name.
+ ///
+ public static string ProfileFile { get; set; }
+
+ ///
+ /// Gets or sets file name for the migration backup.
+ ///
+ public static string OldProfileFileBackup { get; set; }
+
+ ///
+ /// Gets or sets old profile file name.
+ ///
+ public static string OldProfileFile { get; set; }
+
+ static AzureSession()
+ {
+ ClientFactory = new ClientFactory();
+ AuthenticationFactory = new AuthenticationFactory();
+ DataStore = new MemoryDataStore();
+ TokenCache = new TokenCache();
+ OldProfileFile = "WindowsAzureProfile.xml";
+ OldProfileFileBackup = "WindowsAzureProfile.xml.bak";
+ ProfileDirectory = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ Resources.AzureDirectoryName); ;
+ ProfileFile = "AzureProfile.json";
+ TokenCacheFile = "TokenCache.dat";
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Commands.Common.Authentication.csproj b/src/Common/Commands.Common.Authentication/Commands.Common.Authentication.csproj
new file mode 100644
index 000000000000..6d0cd4c93d78
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Commands.Common.Authentication.csproj
@@ -0,0 +1,182 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {D3804B64-C0D3-48F8-82EC-1F632F833C9E}
+ Library
+ Properties
+ Microsoft.Azure.Commands.Common.Authentication
+ Commands.Common.Authentication
+ v4.5
+ 512
+ ..\..\
+ true
+ /assemblyCompareMode:StrongNameIgnoringVersion
+ 06e19c11
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ true
+ true
+ false
+ true
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE;SIGN
+ AnyCPU
+ bin\Release\Management.Utilities.dll.CodeAnalysisLog.xml
+ true
+ GlobalSuppressions.cs
+ prompt
+ MinimumRecommendedRules.ruleset
+ ;$(ProgramFiles)\Microsoft Visual Studio 12.0\Team Tools\Static Analysis Tools\Rule Sets
+ ;$(ProgramFiles)\Microsoft Visual Studio 12.0\Team Tools\Static Analysis Tools\FxCop\Rules
+ true
+ MSSharedLibKey.snk
+ true
+ true
+ false
+ true
+
+
+
+ ..\..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll
+ True
+
+
+ ..\..\packages\Microsoft.Azure.Common.2.1.0\lib\net45\Microsoft.Azure.Common.dll
+ True
+
+
+ ..\..\packages\Microsoft.Azure.Common.2.1.0\lib\net45\Microsoft.Azure.Common.NetFramework.dll
+ True
+
+
+ ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.18.206251556\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
+ True
+
+
+ ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.18.206251556\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll
+ True
+
+
+ ..\..\packages\Microsoft.Rest.ClientRuntime.2.0.1\lib\net45\Microsoft.Rest.ClientRuntime.dll
+ True
+
+
+ ..\..\packages\Microsoft.Rest.ClientRuntime.Azure.3.0.2\lib\net45\Microsoft.Rest.ClientRuntime.Azure.dll
+ True
+
+
+ ..\..\packages\Microsoft.Rest.ClientRuntime.Azure.Authentication.2.0.1-preview\lib\net45\Microsoft.Rest.ClientRuntime.Azure.Authentication.dll
+ True
+
+
+ ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll
+ True
+
+
+ ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll
+ True
+
+
+ ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+
+
+
+
+ ..\..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Common/Commands.Common.Authentication/Common/AzureModule.cs b/src/Common/Commands.Common.Authentication/Common/AzureModule.cs
new file mode 100644
index 000000000000..a5df2c70f95d
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Common/AzureModule.cs
@@ -0,0 +1,23 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public enum AzureModule
+ {
+ AzureServiceManagement,
+ AzureResourceManager,
+ AzureProfile
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Common/ProfileData.cs b/src/Common/Commands.Common.Authentication/Common/ProfileData.cs
new file mode 100644
index 000000000000..25aa6d2b8acc
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Common/ProfileData.cs
@@ -0,0 +1,272 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ ///
+ /// This class provides the representation of
+ /// data loaded and saved into data files
+ /// for AzureSMProfile.
+ ///
+ [DataContract]
+ public class ProfileData
+ {
+ [DataMember]
+ public string DefaultEnvironmentName { get; set; }
+
+ [DataMember]
+ public IEnumerable Environments { get; set; }
+
+ [DataMember]
+ public IEnumerable Subscriptions { get; set; }
+ }
+
+ ///
+ /// This class provides the representation of
+ /// data loaded and saved into data files for
+ /// an individual Azure environment
+ ///
+ [DataContract]
+ public class AzureEnvironmentData
+ {
+ public AzureEnvironment ToAzureEnvironment()
+ {
+ return new AzureEnvironment
+ {
+ Name = this.Name,
+ Endpoints = new Dictionary
+ {
+ { AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId, this.ActiveDirectoryServiceEndpointResourceId },
+ { AzureEnvironment.Endpoint.AdTenant, this.AdTenantUrl },
+ { AzureEnvironment.Endpoint.Gallery, this.GalleryEndpoint },
+ { AzureEnvironment.Endpoint.ManagementPortalUrl, this.ManagementPortalUrl },
+ { AzureEnvironment.Endpoint.PublishSettingsFileUrl, this.PublishSettingsFileUrl },
+ { AzureEnvironment.Endpoint.ResourceManager, this.ResourceManagerEndpoint },
+ { AzureEnvironment.Endpoint.ServiceManagement, this.ServiceEndpoint },
+ { AzureEnvironment.Endpoint.SqlDatabaseDnsSuffix, this.SqlDatabaseDnsSuffix },
+ { AzureEnvironment.Endpoint.StorageEndpointSuffix, this.StorageEndpointSuffix },
+ { AzureEnvironment.Endpoint.AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix, this.AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix },
+ { AzureEnvironment.Endpoint.AzureDataLakeStoreFileSystemEndpointSuffix, this.AzureDataLakeStoreFileSystemEndpointSuffix },
+ }
+ };
+ }
+
+ [DataMember]
+ public string Name { get; set; }
+
+ [DataMember]
+ public string PublishSettingsFileUrl { get; set; }
+
+ [DataMember]
+ public string ServiceEndpoint { get; set; }
+
+ [DataMember]
+ public string ResourceManagerEndpoint { get; set; }
+
+ [DataMember]
+ public string ManagementPortalUrl { get; set; }
+
+ [DataMember]
+ public string StorageEndpointSuffix { get; set; }
+
+ [DataMember]
+ public string AdTenantUrl { get; set; }
+
+ [DataMember]
+ public string CommonTenantId { get; set; }
+
+ [DataMember]
+ public string GalleryEndpoint { get; set; }
+
+ [DataMember]
+ public string ActiveDirectoryServiceEndpointResourceId { get; set; }
+
+ [DataMember]
+ public string SqlDatabaseDnsSuffix { get; set; }
+
+ [DataMember]
+ public string TrafficManagerEndpointSuffix { get; set; }
+
+ [DataMember]
+ public string AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix { get; set; }
+
+ [DataMember]
+ public string AzureDataLakeStoreFileSystemEndpointSuffix { get; set; }
+ }
+
+ ///
+ /// This class provides the representation of data loaded
+ /// and saved into data file for an individual Azure subscription.
+ ///
+ [DataContract]
+ public class AzureSubscriptionData
+ {
+ ///
+ /// Constructor used by DataContractSerializer
+ ///
+ public AzureSubscriptionData()
+ {
+ }
+
+ public AzureSubscription ToAzureSubscription(List envs)
+ {
+ AzureSubscription subscription = new AzureSubscription();
+ try
+ {
+ subscription.Id = new Guid(this.SubscriptionId);
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException("Subscription ID is not a valid GUID.", ex);
+ }
+ subscription.Name = Name;
+
+ // Logic to detect what is the subscription environment relies on having ManagementEndpoint (i.e. RDFE endpoint) set already on the subscription
+ List allEnvs = envs.Union(AzureEnvironment.PublicEnvironments.Values).ToList();
+ AzureEnvironment env = allEnvs.FirstOrDefault(e => e.IsEndpointSetToValue(AzureEnvironment.Endpoint.ServiceManagement, this.ManagementEndpoint));
+
+ if (env != null)
+ {
+ subscription.Environment = env.Name;
+ }
+ else
+ {
+ subscription.Environment = EnvironmentName.AzureCloud;
+ }
+
+ if (!string.IsNullOrEmpty(this.ManagementCertificate))
+ {
+ subscription.Account = this.ManagementCertificate;
+ }
+
+ if (!string.IsNullOrEmpty(this.ActiveDirectoryUserId))
+ {
+ subscription.Account = this.ActiveDirectoryUserId;
+ }
+
+ if (!string.IsNullOrEmpty(this.ActiveDirectoryTenantId))
+ {
+ subscription.SetProperty(AzureSubscription.Property.Tenants, ActiveDirectoryTenantId);
+ }
+
+ if (this.IsDefault)
+ {
+ subscription.SetProperty(AzureSubscription.Property.Default, "True");
+ }
+
+ if (!string.IsNullOrEmpty(this.CloudStorageAccount))
+ {
+ subscription.Properties.Add(AzureSubscription.Property.StorageAccount, this.CloudStorageAccount);
+ }
+
+ if (this.RegisteredResourceProviders.Count() > 0)
+ {
+ StringBuilder providers = new StringBuilder();
+ subscription.Properties.Add(AzureSubscription.Property.RegisteredResourceProviders,
+ string.Join(",", RegisteredResourceProviders));
+ }
+
+ return subscription;
+ }
+
+ public IEnumerable ToAzureAccounts()
+ {
+ if (!string.IsNullOrEmpty(ActiveDirectoryUserId))
+ {
+ AzureAccount userAccount = new AzureAccount
+ {
+ Id = ActiveDirectoryUserId,
+ Type = AzureAccount.AccountType.User
+ };
+
+ userAccount.SetProperty(AzureAccount.Property.Subscriptions, new Guid(this.SubscriptionId).ToString());
+
+ if (!string.IsNullOrEmpty(ActiveDirectoryTenantId))
+ {
+ userAccount.SetProperty(AzureAccount.Property.Tenants, ActiveDirectoryTenantId);
+ }
+
+ yield return userAccount;
+ }
+
+ if (!string.IsNullOrEmpty(ManagementCertificate))
+ {
+ AzureAccount certificateAccount = new AzureAccount
+ {
+ Id = ManagementCertificate,
+ Type = AzureAccount.AccountType.Certificate
+ };
+
+ certificateAccount.SetProperty(AzureAccount.Property.Subscriptions, new Guid(this.SubscriptionId).ToString());
+
+ yield return certificateAccount;
+ }
+ }
+
+ [DataMember]
+ public string Name { get; set; }
+
+ [DataMember]
+ public string SubscriptionId { get; set; }
+
+ [DataMember]
+ public string ManagementEndpoint { get; set; }
+
+ [DataMember]
+ public string ResourceManagerEndpoint { get; set; }
+
+ [DataMember]
+ public string ActiveDirectoryEndpoint { get; set; }
+
+ [DataMember]
+ public string ActiveDirectoryTenantId { get; set; }
+
+ [DataMember]
+ public string ActiveDirectoryUserId { get; set; }
+
+ [DataMember]
+ public string LoginType { get; set; }
+
+ [DataMember]
+ public bool IsDefault { get; set; }
+
+ [DataMember]
+ public string ManagementCertificate { get; set; }
+
+ [DataMember]
+ public string CloudStorageAccount { get; set; }
+
+ [DataMember]
+ public IEnumerable RegisteredResourceProviders { get; set; }
+
+ [DataMember]
+ public string GalleryEndpoint { get; set; }
+
+ [DataMember]
+ public string ActiveDirectoryServiceEndpointResourceId { get; set; }
+
+ [DataMember]
+ public string SqlDatabaseDnsSuffix { get; set; }
+
+ [DataMember]
+ public string TrafficManagerEndpointSuffix { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Common/Commands.Common.Authentication/Common/Validate.cs b/src/Common/Commands.Common.Authentication/Common/Validate.cs
new file mode 100644
index 000000000000..b1f555fa9a9f
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Common/Validate.cs
@@ -0,0 +1,218 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public static class Validate
+ {
+ [Flags]
+ enum InternetConnectionState : int
+ {
+ INTERNET_CONNECTION_MODEM = 0x1,
+ INTERNET_CONNECTION_LAN = 0x2,
+ INTERNET_CONNECTION_PROXY = 0x4,
+ INTERNET_RAS_INSTALLED = 0x10,
+ INTERNET_CONNECTION_OFFLINE = 0x20,
+ INTERNET_CONNECTION_CONFIGURED = 0x40
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Not necessary for a single p-invoke")]
+ [DllImport("WININET", CharSet = CharSet.Auto)]
+ static extern bool InternetGetConnectedState(ref InternetConnectionState lpdwFlags, int dwReserved);
+
+ ///
+ /// Validates against given string if null or empty.
+ ///
+ /// string variable to validate
+ /// This parameter is used when the validation fails. It can contain actual message to display
+ /// or parameter name to display with default message
+ /// Indicates either to use messageData as actual message or parameter name
+ public static void ValidateStringIsNullOrEmpty(string data, string messageData, bool useDefaultMessage = true)
+ {
+ if (string.IsNullOrEmpty(data))
+ {
+ // In this case use messageData parameter as name for null/empty string.
+ if (useDefaultMessage)
+ {
+ throw new ArgumentException(string.Format(Resources.InvalidOrEmptyArgumentMessage, messageData));
+ }
+ else
+ {
+ // Use the message provided by the user
+ throw new ArgumentException(messageData);
+ }
+ }
+ }
+
+ public static void ValidatePathName(string element, string exceptionMessage)
+ {
+ if (element.IndexOfAny(Path.GetInvalidPathChars()) != -1)
+ {
+ throw new ArgumentException(exceptionMessage);
+ }
+ }
+
+ public static void ValidateFileName(string element, string exceptionMessage = null)
+ {
+ try
+ {
+ string fileName = Path.GetFileName(element);
+
+ if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) != -1)
+ {
+ throw new ArgumentException(exceptionMessage ?? string.Empty);
+ }
+ }
+ catch
+ {
+ throw new ArgumentException(exceptionMessage ?? string.Empty);
+ }
+ }
+
+ public static void ValidateFileExists(string filePath, string exceptionMessage)
+ {
+ if (!FileUtilities.DataStore.FileExists(filePath))
+ {
+ throw new FileNotFoundException(exceptionMessage);
+ }
+ }
+
+ public static void ValidateDirectoryExists(string directory, string exceptionMessage = null)
+ {
+ string msg = string.Format(Resources.PathDoesNotExist, directory);
+
+ if (!FileUtilities.DataStore.DirectoryExists(directory))
+ {
+ if (!string.IsNullOrEmpty(exceptionMessage))
+ {
+ msg = exceptionMessage;
+ }
+
+ throw new FileNotFoundException(msg);
+ }
+ }
+
+ public static void ValidateNullArgument(object item, string exceptionMessage)
+ {
+ if (item == null)
+ {
+ throw new ArgumentException(exceptionMessage);
+ }
+ }
+
+ public static void ValidateFileExtention(string filePath, string desiredExtention)
+ {
+ bool invalidExtension = Convert.ToBoolean(string.Compare(Path.GetExtension(filePath), desiredExtention, true));
+
+ if (invalidExtension)
+ {
+ throw new ArgumentException(string.Format(Resources.InvalidFileExtension, filePath, desiredExtention));
+ }
+ }
+
+ public static void ValidateDnsName(string dnsName, string parameterName)
+ {
+ if (Uri.CheckHostName(dnsName) != UriHostNameType.Dns || dnsName.EndsWith("-"))
+ {
+ throw new ArgumentException(string.Format(Resources.InvalidDnsName, dnsName, parameterName));
+ }
+ }
+
+ public static void ValidateDnsDoesNotExist(string dnsName)
+ {
+ try
+ {
+ Dns.GetHostEntry(dnsName);
+ // Dns does exist throw exception
+ //
+ throw new ArgumentException(string.Format(Resources.ServiceNameExists, dnsName));
+ }
+ catch (SocketException)
+ {
+ // Dns doesn't exist
+ }
+ }
+
+ public static void ValidateInternetConnection()
+ {
+ InternetConnectionState flags = 0;
+
+ if (!InternetGetConnectedState(ref flags, 0))
+ {
+ throw new Exception(Resources.NoInternetConnection);
+ }
+ }
+
+ public static void HasWhiteCharacter(string text, string exceptionMessage = null)
+ {
+ if (text.Any(char.IsWhiteSpace))
+ {
+ throw new ArgumentException(exceptionMessage ?? string.Empty);
+ }
+ }
+
+ ///
+ /// Make validation for given path.
+ ///
+ /// Path to validate
+ /// message to display if this validation failed
+ public static void ValidatePath(string path, string exceptionMessage)
+ {
+ ValidateStringIsNullOrEmpty(path, exceptionMessage, false);
+ ValidatePathName(path, exceptionMessage);
+ }
+
+ ///
+ /// Validates against given directory
+ ///
+ /// Directory name
+ /// Name which you use to identify that directory to user (i.e. AzureSdkDirectory)
+ public static void ValidateDirectoryFull(string directoryNameOnDisk, string directoryName)
+ {
+ BasicFileAndDirectoryValidation(directoryNameOnDisk, directoryName);
+ ValidateDirectoryExists(directoryNameOnDisk, string.Format(Resources.PathDoesNotExistForElement, directoryName, directoryNameOnDisk));
+ }
+
+ private static void BasicFileAndDirectoryValidation(string fullPath, string name)
+ {
+ ValidateStringIsNullOrEmpty(fullPath, name);
+ ValidateFileName(fullPath, Resources.IllegalPath);
+ string directoryPath = Path.GetDirectoryName(fullPath);
+ if (!string.IsNullOrEmpty(directoryPath))
+ {
+ ValidatePath(fullPath, Resources.IllegalPath);
+ }
+ }
+
+ ///
+ /// Validates against given file
+ ///
+ /// File name
+ /// Name which you use to identify that directory to user (i.e. Service Settings)
+ public static void ValidateFileFull(string fileNameOnDisk, string fileName)
+ {
+ BasicFileAndDirectoryValidation(fileNameOnDisk, fileName);
+ ValidateFileExists(fileNameOnDisk, string.Format(Resources.PathDoesNotExistForElement, fileName, fileNameOnDisk));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Common/Commands.Common.Authentication/Extensions/CloudExceptionExtensions.cs b/src/Common/Commands.Common.Authentication/Extensions/CloudExceptionExtensions.cs
new file mode 100644
index 000000000000..f51f6698d518
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Extensions/CloudExceptionExtensions.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using Hyak.Common;
+using System.Linq;
+
+namespace Microsoft.Azure.Common
+{
+ public static class CloudExceptionExtensions
+ {
+ public static string GetRequestId(this CloudException exception)
+ {
+ if(exception == null ||
+ exception.Response == null ||
+ exception.Response.Headers == null ||
+ !exception.Response.Headers.Keys.Contains("x-ms-request-id"))
+ {
+ return null;
+ }
+
+ return exception.Response.Headers["x-ms-request-id"].FirstOrDefault();
+
+ }
+ public static string GetRoutingRequestId(this CloudException exception)
+ {
+ if (exception == null ||
+ exception.Response == null ||
+ exception.Response.Headers == null ||
+ !exception.Response.Headers.Keys.Contains("x-ms-routing-request-id"))
+ {
+ return null;
+ }
+
+ return exception.Response.Headers["x-ms-routing-request-id"].FirstOrDefault();
+
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Factories/AuthenticationFactory.cs b/src/Common/Commands.Common.Authentication/Factories/AuthenticationFactory.cs
new file mode 100644
index 000000000000..1d49be6be746
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Factories/AuthenticationFactory.cs
@@ -0,0 +1,302 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using System;
+using System.Linq;
+using System.Security;
+using Hyak.Common;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using Microsoft.Rest;
+using Microsoft.Rest.Azure.Authentication;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Factories
+{
+ public class AuthenticationFactory : IAuthenticationFactory
+ {
+ public const string CommonAdTenant = "Common";
+
+ public AuthenticationFactory()
+ {
+ TokenProvider = new AdalTokenProvider();
+ }
+
+ public ITokenProvider TokenProvider { get; set; }
+
+ public IAccessToken Authenticate(
+ AzureAccount account,
+ AzureEnvironment environment,
+ string tenant,
+ SecureString password,
+ ShowDialog promptBehavior,
+ TokenCache tokenCache,
+ AzureEnvironment.Endpoint resourceId = AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId)
+ {
+ var configuration = GetAdalConfiguration(environment, tenant, resourceId, tokenCache);
+
+ TracingAdapter.Information(Resources.AdalAuthConfigurationTrace, configuration.AdDomain, configuration.AdEndpoint,
+ configuration.ClientId, configuration.ClientRedirectUri, configuration.ResourceClientUri, configuration.ValidateAuthority);
+ IAccessToken token;
+ if (account.IsPropertySet(AzureAccount.Property.CertificateThumbprint))
+ {
+ var thumbprint = account.GetProperty(AzureAccount.Property.CertificateThumbprint);
+ token = TokenProvider.GetAccessTokenWithCertificate(configuration, account.Id, thumbprint, account.Type);
+ }
+ else
+ {
+
+ token = TokenProvider.GetAccessToken(configuration, promptBehavior, account.Id, password, account.Type);
+ }
+
+ account.Id = token.UserId;
+ return token;
+ }
+
+ public IAccessToken Authenticate(
+ AzureAccount account,
+ AzureEnvironment environment,
+ string tenant,
+ SecureString password,
+ ShowDialog promptBehavior,
+ AzureEnvironment.Endpoint resourceId = AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId)
+ {
+ return Authenticate(account, environment, tenant, password, promptBehavior, AzureSession.TokenCache, resourceId);
+ }
+
+ public SubscriptionCloudCredentials GetSubscriptionCloudCredentials(AzureContext context)
+ {
+ return GetSubscriptionCloudCredentials(context, AzureEnvironment.Endpoint.ServiceManagement);
+ }
+
+ public SubscriptionCloudCredentials GetSubscriptionCloudCredentials(AzureContext context, AzureEnvironment.Endpoint targetEndpoint)
+ {
+ if (context.Subscription == null)
+ {
+ var exceptionMessage = targetEndpoint == AzureEnvironment.Endpoint.ServiceManagement
+ ? Resources.InvalidDefaultSubscription
+ : Resources.NoSubscriptionInContext;
+ throw new ApplicationException(exceptionMessage);
+ }
+
+ if (context.Account == null)
+ {
+ var exceptionMessage = targetEndpoint == AzureEnvironment.Endpoint.ServiceManagement
+ ? Resources.AccountNotFound
+ : Resources.ArmAccountNotFound;
+ throw new ArgumentException(exceptionMessage);
+ }
+
+ if (context.Account.Type == AzureAccount.AccountType.Certificate)
+ {
+ var certificate = AzureSession.DataStore.GetCertificate(context.Account.Id);
+ return new CertificateCloudCredentials(context.Subscription.Id.ToString(), certificate);
+ }
+
+ if (context.Account.Type == AzureAccount.AccountType.AccessToken)
+ {
+ return new TokenCloudCredentials(context.Subscription.Id.ToString(), context.Account.GetProperty(AzureAccount.Property.AccessToken));
+ }
+
+ string tenant = null;
+
+ if (context.Subscription != null && context.Account != null)
+ {
+ tenant = context.Subscription.GetPropertyAsArray(AzureSubscription.Property.Tenants)
+ .Intersect(context.Account.GetPropertyAsArray(AzureAccount.Property.Tenants))
+ .FirstOrDefault();
+ }
+
+ if (tenant == null && context.Tenant != null && context.Tenant.Id != Guid.Empty)
+ {
+ tenant = context.Tenant.Id.ToString();
+ }
+
+ if (tenant == null)
+ {
+ var exceptionMessage = targetEndpoint == AzureEnvironment.Endpoint.ServiceManagement
+ ? Resources.TenantNotFound
+ : Resources.NoTenantInContext;
+ throw new ArgumentException(exceptionMessage);
+ }
+
+ try
+ {
+ TracingAdapter.Information(Resources.UPNAuthenticationTrace,
+ context.Account.Id, context.Environment.Name, tenant);
+ var tokenCache = AzureSession.TokenCache;
+ if (context.TokenCache != null && context.TokenCache.Length > 0)
+ {
+ tokenCache = new TokenCache(context.TokenCache);
+ }
+
+ var token = Authenticate(context.Account, context.Environment,
+ tenant, null, ShowDialog.Never, tokenCache, context.Environment.GetTokenAudience(targetEndpoint));
+
+ if (context.TokenCache != null && context.TokenCache.Length > 0)
+ {
+ context.TokenCache = tokenCache.Serialize();
+ }
+
+ TracingAdapter.Information(Resources.UPNAuthenticationTokenTrace,
+ token.LoginType, token.TenantId, token.UserId);
+ return new AccessTokenCredential(context.Subscription.Id, token);
+ }
+ catch (Exception ex)
+ {
+ TracingAdapter.Information(Resources.AdalAuthException, ex.Message);
+ var exceptionMessage = targetEndpoint == AzureEnvironment.Endpoint.ServiceManagement
+ ? Resources.InvalidSubscriptionState
+ : Resources.InvalidArmContext;
+ throw new ArgumentException(exceptionMessage, ex);
+ }
+ }
+
+ public ServiceClientCredentials GetServiceClientCredentials(AzureContext context)
+ {
+ return GetServiceClientCredentials(context,
+ AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId);
+ }
+
+ public ServiceClientCredentials GetServiceClientCredentials(AzureContext context, AzureEnvironment.Endpoint targetEndpoint)
+ {
+ if (context.Account == null)
+ {
+ throw new ArgumentException(Resources.ArmAccountNotFound);
+ }
+
+ if (context.Account.Type == AzureAccount.AccountType.Certificate)
+ {
+ throw new NotSupportedException(AzureAccount.AccountType.Certificate.ToString());
+ }
+
+ if (context.Account.Type == AzureAccount.AccountType.AccessToken)
+ {
+ return new TokenCredentials(context.Account.GetProperty(AzureAccount.Property.AccessToken));
+ }
+
+ string tenant = null;
+
+ if (context.Subscription != null && context.Account != null)
+ {
+ tenant = context.Subscription.GetPropertyAsArray(AzureSubscription.Property.Tenants)
+ .Intersect(context.Account.GetPropertyAsArray(AzureAccount.Property.Tenants))
+ .FirstOrDefault();
+ }
+
+ if (tenant == null && context.Tenant != null && context.Tenant.Id != Guid.Empty)
+ {
+ tenant = context.Tenant.Id.ToString();
+ }
+
+ if (tenant == null)
+ {
+ throw new ArgumentException(Resources.NoTenantInContext);
+ }
+
+ try
+ {
+ TracingAdapter.Information(Resources.UPNAuthenticationTrace,
+ context.Account.Id, context.Environment.Name, tenant);
+
+ // TODO: When we will refactor the code, need to add tracing
+ /*TracingAdapter.Information(Resources.UPNAuthenticationTokenTrace,
+ token.LoginType, token.TenantId, token.UserId);*/
+
+ var env = new ActiveDirectoryServiceSettings
+ {
+ AuthenticationEndpoint = context.Environment.GetEndpointAsUri(AzureEnvironment.Endpoint.ActiveDirectory),
+ TokenAudience = context.Environment.GetEndpointAsUri(context.Environment.GetTokenAudience(targetEndpoint)),
+ ValidateAuthority = !context.Environment.OnPremise
+ };
+
+ var tokenCache = AzureSession.TokenCache;
+
+ if (context.TokenCache != null && context.TokenCache.Length > 0)
+ {
+ tokenCache = new TokenCache(context.TokenCache);
+ }
+
+ ServiceClientCredentials result = null;
+
+ if (context.Account.Type == AzureAccount.AccountType.User)
+ {
+ result = Rest.Azure.Authentication.UserTokenProvider.CreateCredentialsFromCache(
+ AdalConfiguration.PowerShellClientId,
+ tenant,
+ context.Account.Id,
+ env,
+ tokenCache).ConfigureAwait(false).GetAwaiter().GetResult();
+ }
+ else if (context.Account.Type == AzureAccount.AccountType.ServicePrincipal)
+ {
+ if (context.Account.IsPropertySet(AzureAccount.Property.CertificateThumbprint))
+ {
+ result = ApplicationTokenProvider.LoginSilentAsync(
+ tenant,
+ context.Account.Id,
+ new CertificateApplicationCredentialProvider(
+ context.Account.GetProperty(AzureAccount.Property.CertificateThumbprint)),
+ env,
+ tokenCache).ConfigureAwait(false).GetAwaiter().GetResult();
+ }
+ else
+ {
+ result = ApplicationTokenProvider.LoginSilentAsync(
+ tenant,
+ context.Account.Id,
+ new KeyStoreApplicationCredentialProvider(tenant),
+ env,
+ tokenCache).ConfigureAwait(false).GetAwaiter().GetResult();
+ }
+ }
+ else
+ {
+ throw new NotSupportedException(context.Account.Type.ToString());
+ }
+
+ if (context.TokenCache != null && context.TokenCache.Length > 0)
+ {
+ context.TokenCache = tokenCache.Serialize();
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ TracingAdapter.Information(Resources.AdalAuthException, ex.Message);
+ throw new ArgumentException(Resources.InvalidArmContext, ex);
+ }
+ }
+
+ private AdalConfiguration GetAdalConfiguration(AzureEnvironment environment, string tenantId,
+ AzureEnvironment.Endpoint resourceId, TokenCache tokenCache)
+ {
+ if (environment == null)
+ {
+ throw new ArgumentNullException("environment");
+ }
+ var adEndpoint = environment.Endpoints[AzureEnvironment.Endpoint.ActiveDirectory];
+
+ return new AdalConfiguration
+ {
+ AdEndpoint = adEndpoint,
+ ResourceClientUri = environment.Endpoints[resourceId],
+ AdDomain = tenantId,
+ ValidateAuthority = !environment.OnPremise,
+ TokenCache = tokenCache
+ };
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Factories/ClientFactory.cs b/src/Common/Commands.Common.Authentication/Factories/ClientFactory.cs
new file mode 100644
index 000000000000..7ac12cce7418
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Factories/ClientFactory.cs
@@ -0,0 +1,312 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Hyak.Common;
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Factories
+{
+ public class ClientFactory : IClientFactory
+ {
+ private static readonly char[] uriPathSeparator = { '/' };
+
+ private Dictionary _actions;
+ private OrderedDictionary _handlers;
+
+ public ClientFactory()
+ {
+ _actions = new Dictionary();
+ UserAgents = new HashSet();
+ _handlers = new OrderedDictionary();
+ }
+
+ public virtual TClient CreateArmClient(AzureContext context, AzureEnvironment.Endpoint endpoint) where TClient : Microsoft.Rest.ServiceClient
+ {
+ if (context == null)
+ {
+ throw new ApplicationException(Resources.NoSubscriptionInContext);
+ }
+
+ var creds = AzureSession.AuthenticationFactory.GetServiceClientCredentials(context);
+ var newHandlers = GetCustomHandlers();
+ TClient client = (newHandlers == null || newHandlers.Length == 0)
+ ? CreateCustomArmClient(context.Environment.GetEndpointAsUri(endpoint), creds)
+ : CreateCustomArmClient(context.Environment.GetEndpointAsUri(endpoint), creds, GetCustomHandlers());
+
+ var subscriptionId = typeof(TClient).GetProperty("SubscriptionId");
+ if (subscriptionId != null && context.Subscription != null)
+ {
+ subscriptionId.SetValue(client, context.Subscription.Id.ToString());
+ }
+
+ return client;
+ }
+
+ public virtual TClient CreateCustomArmClient(params object[] parameters) where TClient : Microsoft.Rest.ServiceClient
+ {
+ List types = new List();
+ foreach (object obj in parameters)
+ {
+ types.Add(obj.GetType());
+ }
+
+ var constructor = typeof(TClient).GetConstructor(types.ToArray());
+
+ if (constructor == null)
+ {
+ throw new InvalidOperationException(string.Format(Resources.InvalidManagementClientType, typeof(TClient).Name));
+ }
+
+ TClient client = (TClient)constructor.Invoke(parameters);
+
+ foreach (ProductInfoHeaderValue userAgent in UserAgents)
+ {
+ client.UserAgent.Add(userAgent);
+ }
+
+ return client;
+ }
+
+ public virtual TClient CreateClient(AzureContext context, AzureEnvironment.Endpoint endpoint) where TClient : ServiceClient
+ {
+ if (context == null)
+ {
+ var exceptionMessage = endpoint == AzureEnvironment.Endpoint.ServiceManagement
+ ? Resources.InvalidDefaultSubscription
+ : Resources.NoSubscriptionInContext;
+ throw new ApplicationException(exceptionMessage);
+ }
+
+ SubscriptionCloudCredentials creds = AzureSession.AuthenticationFactory.GetSubscriptionCloudCredentials(context, endpoint);
+ TClient client = CreateCustomClient(creds, context.Environment.GetEndpointAsUri(endpoint));
+ foreach(DelegatingHandler handler in GetCustomHandlers())
+ {
+ client.AddHandlerToPipeline(handler);
+ }
+
+ return client;
+ }
+
+ public virtual TClient CreateClient(AzureSMProfile profile, AzureEnvironment.Endpoint endpoint) where TClient : ServiceClient
+ {
+ TClient client = CreateClient(profile.Context, endpoint);
+
+ foreach (IClientAction action in _actions.Values)
+ {
+ action.Apply(client, profile, endpoint);
+ }
+
+ return client;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual TClient CreateClient(AzureSMProfile profile, AzureSubscription subscription, AzureEnvironment.Endpoint endpoint) where TClient : ServiceClient
+ {
+ if (subscription == null)
+ {
+ throw new ApplicationException(Resources.InvalidDefaultSubscription);
+ }
+
+ if (!profile.Accounts.ContainsKey(subscription.Account))
+ {
+ throw new ArgumentException(string.Format("Account with name '{0}' does not exist.", subscription.Account), "accountName");
+ }
+
+ if (!profile.Environments.ContainsKey(subscription.Environment))
+ {
+ throw new ArgumentException(string.Format(Resources.EnvironmentNotFound, subscription.Environment));
+ }
+
+ AzureContext context = new AzureContext(subscription,
+ profile.Accounts[subscription.Account],
+ profile.Environments[subscription.Environment]);
+
+ TClient client = CreateClient(context, endpoint);
+
+ foreach (IClientAction action in _actions.Values)
+ {
+ action.Apply(client, profile, endpoint);
+ }
+
+ return client;
+ }
+
+ public virtual TClient CreateCustomClient(params object[] parameters) where TClient : ServiceClient
+ {
+ List types = new List();
+ foreach (object obj in parameters)
+ {
+ types.Add(obj.GetType());
+ }
+
+ var constructor = typeof(TClient).GetConstructor(types.ToArray());
+
+ if (constructor == null)
+ {
+ throw new InvalidOperationException(string.Format(Resources.InvalidManagementClientType, typeof(TClient).Name));
+ }
+
+ TClient client = (TClient)constructor.Invoke(parameters);
+
+ foreach (ProductInfoHeaderValue userAgent in UserAgents)
+ {
+ client.UserAgent.Add(userAgent);
+ }
+
+ return client;
+ }
+
+ public virtual HttpClient CreateHttpClient(string endpoint, ICredentials credentials)
+ {
+ return CreateHttpClient(endpoint, CreateHttpClientHandler(endpoint, credentials));
+ }
+
+ public virtual HttpClient CreateHttpClient(string endpoint, HttpMessageHandler effectiveHandler)
+ {
+ if (endpoint == null)
+ {
+ throw new ArgumentNullException("endpoint");
+ }
+
+ Uri serviceAddr = new Uri(endpoint);
+ HttpClient client = new HttpClient(effectiveHandler)
+ {
+ BaseAddress = serviceAddr,
+ MaxResponseContentBufferSize = 30 * 1024 * 1024
+ };
+
+ client.DefaultRequestHeaders.Accept.Clear();
+
+ return client;
+ }
+
+ public static HttpClientHandler CreateHttpClientHandler(string endpoint, ICredentials credentials)
+ {
+ if (endpoint == null)
+ {
+ throw new ArgumentNullException("endpoint");
+ }
+
+ // Set up our own HttpClientHandler and configure it
+ HttpClientHandler clientHandler = new HttpClientHandler();
+
+ if (credentials != null)
+ {
+ // Set up credentials cache which will handle basic authentication
+ CredentialCache credentialCache = new CredentialCache();
+
+ // Get base address without terminating slash
+ string credentialAddress = new Uri(endpoint).GetLeftPart(UriPartial.Authority).TrimEnd(uriPathSeparator);
+
+ // Add credentials to cache and associate with handler
+ NetworkCredential networkCredentials = credentials.GetCredential(new Uri(credentialAddress), "Basic");
+ credentialCache.Add(new Uri(credentialAddress), "Basic", networkCredentials);
+ clientHandler.Credentials = credentialCache;
+ clientHandler.PreAuthenticate = true;
+ }
+
+ // Our handler is ready
+ return clientHandler;
+ }
+
+ public void AddAction(IClientAction action)
+ {
+ if (action != null)
+ {
+ action.ClientFactory = this;
+ _actions[action.GetType()] = action;
+ }
+ }
+
+ public void RemoveAction(Type actionType)
+ {
+ if (_actions.ContainsKey(actionType))
+ {
+ _actions.Remove(actionType);
+ }
+ }
+
+ public void AddHandler(T handler) where T: DelegatingHandler, ICloneable
+ {
+ if (handler != null)
+ {
+ _handlers[handler.GetType()] = handler;
+ }
+ }
+
+ public void RemoveHandler(Type handlerType)
+ {
+ if (_handlers.Contains(handlerType))
+ {
+ _handlers.Remove(handlerType);
+ }
+ }
+
+ ///
+ /// Adds user agent to UserAgents collection.
+ ///
+ /// Product name.
+ /// Product version.
+ public void AddUserAgent(string productName, string productVersion)
+ {
+ UserAgents.Add(new ProductInfoHeaderValue(productName, productVersion));
+ }
+
+ ///
+ /// Adds user agent to UserAgents collection with empty version.
+ ///
+ /// Product name.
+ public void AddUserAgent(string productName)
+ {
+ AddUserAgent(productName, "");
+ }
+
+ public HashSet UserAgents { get; set; }
+
+ private DelegatingHandler[] GetCustomHandlers()
+ {
+ List newHandlers = new List();
+ var enumerator = _handlers.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ var handler = enumerator.Value;
+ ICloneable cloneableHandler = handler as ICloneable;
+ if (cloneableHandler != null)
+ {
+ var newHandler = cloneableHandler.Clone();
+ DelegatingHandler convertedHandler = newHandler as DelegatingHandler;
+ if (convertedHandler != null)
+ {
+ newHandlers.Add(convertedHandler);
+ }
+ }
+ }
+
+ return newHandlers.ToArray();
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Interfaces/IAuthenticationFactory.cs b/src/Common/Commands.Common.Authentication/Interfaces/IAuthenticationFactory.cs
new file mode 100644
index 000000000000..62b4cfdffad1
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Interfaces/IAuthenticationFactory.cs
@@ -0,0 +1,70 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using Microsoft.Rest;
+using System.Security;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public interface IAuthenticationFactory
+ {
+ ///
+ /// Returns IAccessToken if authentication succeeds or throws an exception if authentication fails.
+ ///
+ /// The azure account object
+ /// The azure environment object
+ /// The AD tenant in most cases should be 'common'
+ /// The AD account password
+ /// The prompt behavior
+ /// Token Cache
+ /// Optional, the AD resource id
+ ///
+ IAccessToken Authenticate(
+ AzureAccount account,
+ AzureEnvironment environment,
+ string tenant,
+ SecureString password,
+ ShowDialog promptBehavior,
+ TokenCache tokenCache,
+ AzureEnvironment.Endpoint resourceId = AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId);
+
+ ///
+ /// Returns IAccessToken if authentication succeeds or throws an exception if authentication fails.
+ ///
+ /// The azure account object
+ /// The azure environment object
+ /// The AD tenant in most cases should be 'common'
+ /// The AD account password
+ /// The prompt behavior
+ /// Optional, the AD resource id
+ ///
+ IAccessToken Authenticate(
+ AzureAccount account,
+ AzureEnvironment environment,
+ string tenant,
+ SecureString password,
+ ShowDialog promptBehavior,
+ AzureEnvironment.Endpoint resourceId = AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId);
+
+ SubscriptionCloudCredentials GetSubscriptionCloudCredentials(AzureContext context);
+ SubscriptionCloudCredentials GetSubscriptionCloudCredentials(AzureContext context, AzureEnvironment.Endpoint targetEndpoint);
+
+ ServiceClientCredentials GetServiceClientCredentials(AzureContext context);
+
+ ServiceClientCredentials GetServiceClientCredentials(AzureContext context,
+ AzureEnvironment.Endpoint targetEndpoint);
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Interfaces/IClientFactory.cs b/src/Common/Commands.Common.Authentication/Interfaces/IClientFactory.cs
new file mode 100644
index 000000000000..eda7b9073431
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Interfaces/IClientFactory.cs
@@ -0,0 +1,66 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Hyak.Common;
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public interface IClientFactory
+ {
+ TClient CreateArmClient(AzureContext context, AzureEnvironment.Endpoint endpoint) where TClient : Microsoft.Rest.ServiceClient;
+
+ TClient CreateCustomArmClient(params object[] parameters) where TClient : Microsoft.Rest.ServiceClient;
+
+ TClient CreateClient(AzureContext context, AzureEnvironment.Endpoint endpoint) where TClient : ServiceClient;
+
+ TClient CreateClient(AzureSMProfile profile, AzureEnvironment.Endpoint endpoint) where TClient : ServiceClient;
+
+ TClient CreateClient(AzureSMProfile profile, AzureSubscription subscription, AzureEnvironment.Endpoint endpoint) where TClient : ServiceClient;
+
+ TClient CreateCustomClient(params object[] parameters) where TClient : ServiceClient;
+
+ HttpClient CreateHttpClient(string endpoint, ICredentials credentials);
+
+ HttpClient CreateHttpClient(string endpoint, HttpMessageHandler effectiveHandler);
+
+ void AddAction(IClientAction action);
+
+ void RemoveAction(Type actionType);
+
+ void AddHandler(T handler) where T: DelegatingHandler, ICloneable;
+
+ void RemoveHandler(Type handlerType);
+
+ ///
+ /// Adds user agent to UserAgents collection with empty version.
+ ///
+ /// Product name.
+ void AddUserAgent(string productName);
+
+ ///
+ /// Adds user agent to UserAgents collection.
+ ///
+ /// Product name.
+ /// Product version.
+ void AddUserAgent(string productName, string productVersion);
+
+ HashSet UserAgents { get; set; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Interfaces/IDataStore.cs b/src/Common/Commands.Common.Authentication/Interfaces/IDataStore.cs
new file mode 100644
index 000000000000..22121f1fd861
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Interfaces/IDataStore.cs
@@ -0,0 +1,67 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public interface IDataStore
+ {
+ void WriteFile(string path, string contents);
+
+ void WriteFile(string path, string content, Encoding encoding);
+
+ void WriteFile(string path, byte[] contents);
+
+ string ReadFileAsText(string path);
+
+ Stream ReadFileAsStream(string path);
+
+ byte[] ReadFileAsBytes(string path);
+
+ void RenameFile(string oldPath, string newPath);
+
+ void CopyFile(string oldPath, string newPath);
+
+ bool FileExists(string path);
+
+ void DeleteFile(string path);
+
+ void DeleteDirectory(string dir);
+
+ void EmptyDirectory(string dirPath);
+
+ bool DirectoryExists(string path);
+
+ void CreateDirectory(string path);
+
+ string[] GetDirectories(string sourceDirName);
+
+ string[] GetDirectories(string startDirectory, string filePattern, SearchOption options);
+
+ string[] GetFiles(string sourceDirName);
+
+ string[] GetFiles(string startDirectory, string filePattern, SearchOption options);
+
+ FileAttributes GetFileAttributes(string path);
+
+ X509Certificate2 GetCertificate(string thumbprint);
+
+ void AddCertificate(X509Certificate2 cert);
+
+ void RemoveCertificate(string thumbprint);
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Interfaces/IProfileSerializer.cs b/src/Common/Commands.Common.Authentication/Interfaces/IProfileSerializer.cs
new file mode 100644
index 000000000000..a847da23d859
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Interfaces/IProfileSerializer.cs
@@ -0,0 +1,28 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public interface IProfileSerializer
+ {
+ string Serialize(AzureSMProfile profile);
+
+ bool Deserialize(string contents, AzureSMProfile profile);
+
+ IList DeserializeErrors { get; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureAccount.Methods.cs b/src/Common/Commands.Common.Authentication/Models/AzureAccount.Methods.cs
new file mode 100644
index 000000000000..4923ee75562c
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureAccount.Methods.cs
@@ -0,0 +1,145 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ public partial class AzureAccount
+ {
+ public AzureAccount()
+ {
+ Properties = new Dictionary();
+ }
+
+ public string GetProperty(Property property)
+ {
+ return Properties.GetProperty(property);
+ }
+
+ public string[] GetPropertyAsArray(Property property)
+ {
+ return Properties.GetPropertyAsArray(property);
+ }
+
+ public void SetProperty(Property property, params string[] values)
+ {
+ Properties.SetProperty(property, values);
+ }
+
+ public void SetOrAppendProperty(Property property, params string[] values)
+ {
+ Properties.SetOrAppendProperty(property, values);
+ }
+
+ public bool IsPropertySet(Property property)
+ {
+ return Properties.IsPropertySet(property);
+ }
+
+ public List GetSubscriptions(AzureSMProfile profile)
+ {
+ string subscriptions = string.Empty;
+ List subscriptionsList = new List();
+ if (Properties.ContainsKey(Property.Subscriptions))
+ {
+ subscriptions = Properties[Property.Subscriptions];
+ }
+
+ foreach (var subscription in subscriptions.Split(new [] {','}, StringSplitOptions.RemoveEmptyEntries))
+ {
+ try
+ {
+ Guid subscriptionId = new Guid(subscription);
+ Debug.Assert(profile.Subscriptions.ContainsKey(subscriptionId));
+ subscriptionsList.Add(profile.Subscriptions[subscriptionId]);
+ }
+ catch
+ {
+ // Skip
+ }
+ }
+
+ return subscriptionsList;
+ }
+
+ public bool HasSubscription(Guid subscriptionId)
+ {
+ bool exists = false;
+ string subscriptions = GetProperty(Property.Subscriptions);
+
+ if (!string.IsNullOrEmpty(subscriptions))
+ {
+ exists = subscriptions.Contains(subscriptionId.ToString());
+ }
+
+ return exists;
+ }
+
+ public void SetSubscriptions(List subscriptions)
+ {
+ if (subscriptions == null || subscriptions.Count == 0)
+ {
+ if (Properties.ContainsKey(Property.Subscriptions))
+ {
+ Properties.Remove(Property.Subscriptions);
+ }
+ }
+ else
+ {
+ string value = string.Join(",", subscriptions.Select(s => s.Id.ToString()));
+ Properties[Property.Subscriptions] = value;
+ }
+ }
+
+ public void RemoveSubscription(Guid id)
+ {
+ if (HasSubscription(id))
+ {
+ var remainingSubscriptions = GetPropertyAsArray(Property.Subscriptions).Where(s => s != id.ToString()).ToArray();
+
+ if (remainingSubscriptions.Any())
+ {
+ Properties[Property.Subscriptions] = string.Join(",", remainingSubscriptions);
+ }
+ else
+ {
+ Properties.Remove(Property.Subscriptions);
+ }
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ var anotherAccount = obj as AzureAccount;
+ if (anotherAccount == null)
+ {
+ return false;
+ }
+ else
+ {
+ return string.Equals(anotherAccount.Id, Id, StringComparison.InvariantCultureIgnoreCase);
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureAccount.cs b/src/Common/Commands.Common.Authentication/Models/AzureAccount.cs
new file mode 100644
index 000000000000..334a547150f1
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureAccount.cs
@@ -0,0 +1,60 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ [Serializable]
+ public partial class AzureAccount
+ {
+ public string Id { get; set; }
+
+ public AccountType Type { get; set; }
+
+ public Dictionary Properties { get; set; }
+
+ public enum AccountType
+ {
+ Certificate,
+ User,
+ ServicePrincipal,
+ AccessToken
+ }
+
+ public enum Property
+ {
+ ///
+ /// Comma separated list of subscription ids on this account.
+ ///
+ Subscriptions,
+
+ ///
+ /// Comma separated list of tenants on this account.
+ ///
+ Tenants,
+
+ ///
+ /// Access token.
+ ///
+ AccessToken,
+
+ ///
+ /// Thumbprint for associated certificate
+ ///
+ CertificateThumbprint
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureContext.cs b/src/Common/Commands.Common.Authentication/Models/AzureContext.cs
new file mode 100644
index 000000000000..c5c4f3c20ae8
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureContext.cs
@@ -0,0 +1,90 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Newtonsoft.Json;
+using System;
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ ///
+ /// Represents current Azure session context.
+ ///
+ [Serializable]
+ public class AzureContext
+ {
+ ///
+ /// Creates new instance of AzureContext.
+ ///
+ /// The azure subscription object
+ /// The azure account object
+ /// The azure environment object
+ public AzureContext(AzureSubscription subscription, AzureAccount account, AzureEnvironment environment)
+ : this(subscription, account, environment, null)
+ {
+
+ }
+
+ ///
+ /// Creates new instance of AzureContext.
+ ///
+ /// The azure account object
+ /// The azure environment object
+ /// The azure tenant object
+ public AzureContext(AzureAccount account, AzureEnvironment environment, AzureTenant tenant)
+ : this(null, account, environment, tenant)
+ {
+
+ }
+
+ ///
+ /// Creates new instance of AzureContext.
+ ///
+ /// The azure subscription object
+ /// The azure account object
+ /// The azure environment object
+ /// The azure tenant object
+ [JsonConstructor]
+ public AzureContext(AzureSubscription subscription, AzureAccount account, AzureEnvironment environment, AzureTenant tenant)
+ {
+ Subscription = subscription;
+ Account = account;
+ Environment = environment;
+ Tenant = tenant;
+ }
+
+ ///
+ /// Gets the azure account.
+ ///
+ public AzureAccount Account { get; private set; }
+
+ ///
+ /// Gets the azure subscription.
+ ///
+ public AzureSubscription Subscription { get; private set; }
+
+ ///
+ /// Gets the azure environment.
+ ///
+ public AzureEnvironment Environment { get; private set; }
+
+ ///
+ /// Gets the azure tenant.
+ ///
+ public AzureTenant Tenant { get; private set; }
+
+ ///
+ /// Gets or sets the token cache contents.
+ ///
+ public byte[] TokenCache { get; set; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureEnvironment.Methods.cs b/src/Common/Commands.Common.Authentication/Models/AzureEnvironment.Methods.cs
new file mode 100644
index 000000000000..e03beb5dd6e0
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureEnvironment.Methods.cs
@@ -0,0 +1,422 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using Microsoft.Azure.Commands.Common.Authentication.Utilities;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ public partial class AzureEnvironment
+ {
+ ///
+ /// Predefined Microsoft Azure environments
+ ///
+ public static Dictionary PublicEnvironments
+ {
+ get { return environments; }
+ }
+
+ private const string storageFormatTemplate = "{{0}}://{{1}}.{0}.{1}/";
+
+ private string EndpointFormatFor(string service)
+ {
+ string suffix = GetEndpointSuffix(AzureEnvironment.Endpoint.StorageEndpointSuffix);
+
+ if (!string.IsNullOrEmpty(suffix))
+ {
+ suffix = string.Format(storageFormatTemplate, service, suffix);
+ }
+
+ return suffix;
+ }
+
+ ///
+ /// The storage service blob endpoint format.
+ ///
+ private string StorageBlobEndpointFormat()
+ {
+ return EndpointFormatFor("blob");
+ }
+
+ ///
+ /// The storage service queue endpoint format.
+ ///
+ private string StorageQueueEndpointFormat()
+ {
+ return EndpointFormatFor("queue");
+ }
+
+ ///
+ /// The storage service table endpoint format.
+ ///
+ private string StorageTableEndpointFormat()
+ {
+ return EndpointFormatFor("table");
+ }
+
+ ///
+ /// The storage service file endpoint format.
+ ///
+ private string StorageFileEndpointFormat()
+ {
+ return EndpointFormatFor("file");
+ }
+
+ private static readonly Dictionary environments =
+ new Dictionary(StringComparer.InvariantCultureIgnoreCase)
+ {
+ {
+ EnvironmentName.AzureCloud,
+ new AzureEnvironment
+ {
+ Name = EnvironmentName.AzureCloud,
+ Endpoints = new Dictionary
+ {
+ { AzureEnvironment.Endpoint.PublishSettingsFileUrl, AzureEnvironmentConstants.AzurePublishSettingsFileUrl },
+ { AzureEnvironment.Endpoint.ServiceManagement, AzureEnvironmentConstants.AzureServiceEndpoint },
+ { AzureEnvironment.Endpoint.ResourceManager, AzureEnvironmentConstants.AzureResourceManagerEndpoint },
+ { AzureEnvironment.Endpoint.ManagementPortalUrl, AzureEnvironmentConstants.AzureManagementPortalUrl },
+ { AzureEnvironment.Endpoint.ActiveDirectory, AzureEnvironmentConstants.AzureActiveDirectoryEndpoint },
+ { AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId, AzureEnvironmentConstants.AzureServiceEndpoint },
+ { AzureEnvironment.Endpoint.StorageEndpointSuffix, AzureEnvironmentConstants.AzureStorageEndpointSuffix },
+ { AzureEnvironment.Endpoint.Gallery, AzureEnvironmentConstants.GalleryEndpoint },
+ { AzureEnvironment.Endpoint.SqlDatabaseDnsSuffix, AzureEnvironmentConstants.AzureSqlDatabaseDnsSuffix },
+ { AzureEnvironment.Endpoint.Graph, AzureEnvironmentConstants.AzureGraphEndpoint },
+ { AzureEnvironment.Endpoint.TrafficManagerDnsSuffix, AzureEnvironmentConstants.AzureTrafficManagerDnsSuffix },
+ { AzureEnvironment.Endpoint.AzureKeyVaultDnsSuffix, AzureEnvironmentConstants.AzureKeyVaultDnsSuffix},
+ { AzureEnvironment.Endpoint.AzureKeyVaultServiceEndpointResourceId, AzureEnvironmentConstants.AzureKeyVaultServiceEndpointResourceId},
+ { AzureEnvironment.Endpoint.AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix, AzureEnvironmentConstants.AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix},
+ { AzureEnvironment.Endpoint.AzureDataLakeStoreFileSystemEndpointSuffix, AzureEnvironmentConstants.AzureDataLakeStoreFileSystemEndpointSuffix},
+ { AzureEnvironment.Endpoint.GraphEndpointResourceId, AzureEnvironmentConstants.AzureGraphEndpoint}
+ }
+ }
+ },
+ {
+ EnvironmentName.AzureChinaCloud,
+ new AzureEnvironment
+ {
+ Name = EnvironmentName.AzureChinaCloud,
+ Endpoints = new Dictionary
+ {
+ { AzureEnvironment.Endpoint.PublishSettingsFileUrl, AzureEnvironmentConstants.ChinaPublishSettingsFileUrl },
+ { AzureEnvironment.Endpoint.ServiceManagement, AzureEnvironmentConstants.ChinaServiceEndpoint },
+ { AzureEnvironment.Endpoint.ResourceManager, AzureEnvironmentConstants.ChinaResourceManagerEndpoint },
+ { AzureEnvironment.Endpoint.ManagementPortalUrl, AzureEnvironmentConstants.ChinaManagementPortalUrl },
+ { AzureEnvironment.Endpoint.ActiveDirectory, AzureEnvironmentConstants.ChinaActiveDirectoryEndpoint },
+ { AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId, AzureEnvironmentConstants.ChinaServiceEndpoint },
+ { AzureEnvironment.Endpoint.StorageEndpointSuffix, AzureEnvironmentConstants.ChinaStorageEndpointSuffix },
+ { AzureEnvironment.Endpoint.Gallery, AzureEnvironmentConstants.ChinaGalleryEndpoint },
+ { AzureEnvironment.Endpoint.SqlDatabaseDnsSuffix, AzureEnvironmentConstants.ChinaSqlDatabaseDnsSuffix },
+ { AzureEnvironment.Endpoint.Graph, AzureEnvironmentConstants.ChinaGraphEndpoint },
+ { AzureEnvironment.Endpoint.TrafficManagerDnsSuffix, AzureEnvironmentConstants.ChinaTrafficManagerDnsSuffix },
+ { AzureEnvironment.Endpoint.AzureKeyVaultDnsSuffix, AzureEnvironmentConstants.ChinaKeyVaultDnsSuffix },
+ { AzureEnvironment.Endpoint.AzureKeyVaultServiceEndpointResourceId, AzureEnvironmentConstants.ChinaKeyVaultServiceEndpointResourceId },
+ { AzureEnvironment.Endpoint.GraphEndpointResourceId, AzureEnvironmentConstants.ChinaGraphEndpoint}
+ // TODO: DataLakeAnalytics and ADL do not have a China endpoint yet. Once they do, add them here.
+ }
+ }
+ },
+ {
+ EnvironmentName.AzureUSGovernment,
+ new AzureEnvironment
+ {
+ Name = EnvironmentName.AzureUSGovernment,
+ Endpoints = new Dictionary
+ {
+ { AzureEnvironment.Endpoint.PublishSettingsFileUrl, AzureEnvironmentConstants.USGovernmentPublishSettingsFileUrl },
+ { AzureEnvironment.Endpoint.ServiceManagement, AzureEnvironmentConstants.USGovernmentServiceEndpoint },
+ { AzureEnvironment.Endpoint.ResourceManager, AzureEnvironmentConstants.USGovernmentResourceManagerEndpoint },
+ { AzureEnvironment.Endpoint.ManagementPortalUrl, AzureEnvironmentConstants.USGovernmentManagementPortalUrl },
+ { AzureEnvironment.Endpoint.ActiveDirectory, AzureEnvironmentConstants.USGovernmentActiveDirectoryEndpoint },
+ { AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId, AzureEnvironmentConstants.USGovernmentServiceEndpoint },
+ { AzureEnvironment.Endpoint.StorageEndpointSuffix, AzureEnvironmentConstants.USGovernmentStorageEndpointSuffix },
+ { AzureEnvironment.Endpoint.Gallery, AzureEnvironmentConstants.USGovernmentGalleryEndpoint },
+ { AzureEnvironment.Endpoint.SqlDatabaseDnsSuffix, AzureEnvironmentConstants.USGovernmentSqlDatabaseDnsSuffix },
+ { AzureEnvironment.Endpoint.Graph, AzureEnvironmentConstants.USGovernmentGraphEndpoint },
+ { AzureEnvironment.Endpoint.TrafficManagerDnsSuffix, null },
+ { AzureEnvironment.Endpoint.AzureKeyVaultDnsSuffix, AzureEnvironmentConstants.USGovernmentKeyVaultDnsSuffix},
+ { AzureEnvironment.Endpoint.AzureKeyVaultServiceEndpointResourceId, AzureEnvironmentConstants.USGovernmentKeyVaultServiceEndpointResourceId},
+ { AzureEnvironment.Endpoint.AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix, null},
+ { AzureEnvironment.Endpoint.AzureDataLakeStoreFileSystemEndpointSuffix, null},
+ {AzureEnvironment.Endpoint.GraphEndpointResourceId, AzureEnvironmentConstants.USGovernmentGraphEndpoint}
+ }
+ }
+ }
+ };
+
+ public Uri GetEndpointAsUri(AzureEnvironment.Endpoint endpoint)
+ {
+ if (Endpoints.ContainsKey(endpoint))
+ {
+ return new Uri(Endpoints[endpoint]);
+ }
+
+ return null;
+ }
+
+ public string GetEndpoint(AzureEnvironment.Endpoint endpoint)
+ {
+ if (Endpoints.ContainsKey(endpoint))
+ {
+ return Endpoints[endpoint];
+ }
+
+ return null;
+ }
+
+ public AzureEnvironment.Endpoint GetTokenAudience(AzureEnvironment.Endpoint targetEndpoint)
+ {
+ return targetEndpoint == AzureEnvironment.Endpoint.Graph
+ ? AzureEnvironment.Endpoint.GraphEndpointResourceId
+ : AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId;
+ }
+
+
+
+ public bool IsEndpointSet(AzureEnvironment.Endpoint endpoint)
+ {
+ return Endpoints.IsPropertySet(endpoint);
+ }
+
+ public bool IsEndpointSetToValue(AzureEnvironment.Endpoint endpoint, string url)
+ {
+ if (url == null && !Endpoints.IsPropertySet(endpoint))
+ {
+ return true;
+ }
+ if (url != null && Endpoints.IsPropertySet(endpoint))
+ {
+ return GetEndpoint(endpoint)
+ .Trim(new[] { '/' })
+ .Equals(url.Trim(new[] { '/' }), StringComparison.InvariantCultureIgnoreCase);
+ }
+ return false;
+ }
+
+ public string GetEndpointSuffix(AzureEnvironment.Endpoint endpointSuffix)
+ {
+ if (Endpoints.ContainsKey(endpointSuffix))
+ {
+ return Endpoints[endpointSuffix];
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the endpoint for storage blob.
+ ///
+ /// The account name
+ /// Use Https when creating the URI. Defaults to true.
+ /// The fully qualified uri to the blob service
+ public Uri GetStorageBlobEndpoint(string accountName, bool useHttps = true)
+ {
+ return new Uri(string.Format(StorageBlobEndpointFormat(), useHttps ? "https" : "http", accountName));
+ }
+
+ ///
+ /// Gets the endpoint for storage queue.
+ ///
+ /// The account name
+ /// Use Https when creating the URI. Defaults to true.
+ /// The fully qualified uri to the queue service
+ public Uri GetStorageQueueEndpoint(string accountName, bool useHttps = true)
+ {
+ return new Uri(string.Format(StorageQueueEndpointFormat(), useHttps ? "https" : "http", accountName));
+ }
+
+ ///
+ /// Gets the endpoint for storage table.
+ ///
+ /// The account name
+ /// Use Https when creating the URI. Defaults to true.
+ /// The fully qualified uri to the table service
+ public Uri GetStorageTableEndpoint(string accountName, bool useHttps = true)
+ {
+ return new Uri(string.Format(StorageTableEndpointFormat(), useHttps ? "https" : "http", accountName));
+ }
+
+ ///
+ /// Gets the endpoint for storage file.
+ ///
+ /// The account name
+ /// Use Https when creating the URI. Defaults to true.
+ /// The fully qualified uri to the file service
+ public Uri GetStorageFileEndpoint(string accountName, bool useHttps = true)
+ {
+ return new Uri(string.Format(StorageFileEndpointFormat(), useHttps ? "https" : "http", accountName));
+ }
+
+ ///
+ /// Gets the management portal URI with a particular realm suffix if supplied
+ ///
+ /// Realm for user's account
+ /// Url to management portal.
+ public string GetManagementPortalUrlWithRealm(string realm = null)
+ {
+ if (realm != null)
+ {
+ realm = string.Format(Resources.PublishSettingsFileRealmFormat, realm);
+ }
+ else
+ {
+ realm = string.Empty;
+ }
+ return GetEndpointAsUri(Endpoint.ManagementPortalUrl) + realm;
+ }
+
+ ///
+ /// Get the publish settings file download url with a realm suffix if needed.
+ ///
+ /// Realm for user's account
+ /// Url to publish settings file
+ public string GetPublishSettingsFileUrlWithRealm(string realm = null)
+ {
+ if (realm != null)
+ {
+ realm = string.Format(Resources.PublishSettingsFileRealmFormat, realm);
+ }
+ else
+ {
+ realm = string.Empty;
+ }
+ return GetEndpointAsUri(Endpoint.PublishSettingsFileUrl) + realm;
+ }
+
+ public enum Endpoint
+ {
+ ActiveDirectoryServiceEndpointResourceId,
+
+ AdTenant,
+
+ Gallery,
+
+ ManagementPortalUrl,
+
+ ServiceManagement,
+
+ PublishSettingsFileUrl,
+
+ ResourceManager,
+
+ SqlDatabaseDnsSuffix,
+
+ StorageEndpointSuffix,
+
+ ActiveDirectory,
+
+ Graph,
+
+ TrafficManagerDnsSuffix,
+
+ AzureKeyVaultDnsSuffix,
+
+ AzureKeyVaultServiceEndpointResourceId,
+
+ AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix,
+
+ AzureDataLakeStoreFileSystemEndpointSuffix,
+
+ GraphEndpointResourceId
+ }
+ }
+
+ public static class EnvironmentName
+ {
+ public const string AzureCloud = "AzureCloud";
+
+ public const string AzureChinaCloud = "AzureChinaCloud";
+
+ public const string AzureUSGovernment = "AzureUSGovernment";
+ }
+
+ public static class AzureEnvironmentConstants
+ {
+ public const string AzureServiceEndpoint = "https://management.core.windows.net/";
+
+ public const string ChinaServiceEndpoint = "https://management.core.chinacloudapi.cn/";
+
+ public const string USGovernmentServiceEndpoint = "https://management.core.usgovcloudapi.net/";
+
+ public const string AzureResourceManagerEndpoint = "https://management.azure.com/";
+
+ public const string ChinaResourceManagerEndpoint = "https://management.chinacloudapi.cn/";
+
+ public const string USGovernmentResourceManagerEndpoint = "https://management.usgovcloudapi.net/";
+
+ public const string GalleryEndpoint = "https://gallery.azure.com/";
+
+ public const string ChinaGalleryEndpoint = "https://gallery.chinacloudapi.cn/";
+
+ public const string USGovernmentGalleryEndpoint = "https://gallery.usgovcloudapi.net/";
+
+ public const string AzurePublishSettingsFileUrl = "http://go.microsoft.com/fwlink/?LinkID=301775";
+
+ public const string ChinaPublishSettingsFileUrl = "http://go.microsoft.com/fwlink/?LinkID=301776";
+
+ public const string USGovernmentPublishSettingsFileUrl = "https://manage.windowsazure.us/publishsettings/index";
+
+ public const string AzureManagementPortalUrl = "http://go.microsoft.com/fwlink/?LinkId=254433";
+
+ public const string ChinaManagementPortalUrl = "http://go.microsoft.com/fwlink/?LinkId=301902";
+
+ public const string USGovernmentManagementPortalUrl = "https://manage.windowsazure.us";
+
+ public const string AzureStorageEndpointSuffix = "core.windows.net";
+
+ public const string ChinaStorageEndpointSuffix = "core.chinacloudapi.cn";
+
+ public const string USGovernmentStorageEndpointSuffix = "core.usgovcloudapi.net";
+
+ public const string AzureSqlDatabaseDnsSuffix = ".database.windows.net";
+
+ public const string ChinaSqlDatabaseDnsSuffix = ".database.chinacloudapi.cn";
+
+ public const string USGovernmentSqlDatabaseDnsSuffix = ".database.usgovcloudapi.net";
+
+ public const string AzureActiveDirectoryEndpoint = "https://login.microsoftonline.com/";
+
+ public const string ChinaActiveDirectoryEndpoint = "https://login.chinacloudapi.cn/";
+
+ public const string USGovernmentActiveDirectoryEndpoint = "https://login.microsoftonline.com/";
+
+ public const string AzureGraphEndpoint = "https://graph.windows.net/";
+
+ public const string ChinaGraphEndpoint = "https://graph.chinacloudapi.cn/";
+
+ public const string USGovernmentGraphEndpoint = "https://graph.windows.net/";
+
+ public const string AzureTrafficManagerDnsSuffix = "trafficmanager.net";
+
+ public const string ChinaTrafficManagerDnsSuffix = "trafficmanager.cn";
+
+ public const string AzureKeyVaultDnsSuffix = "vault.azure.net";
+
+ public const string ChinaKeyVaultDnsSuffix = "vault.azure.cn";
+
+ public const string USGovernmentKeyVaultDnsSuffix = "vault.usgovcloudapi.net";
+
+ public const string AzureKeyVaultServiceEndpointResourceId = "https://vault.azure.net";
+
+ public const string ChinaKeyVaultServiceEndpointResourceId = "https://vault.azure.cn";
+
+ public const string USGovernmentKeyVaultServiceEndpointResourceId = "https://vault.usgovcloudapi.net";
+
+ public const string AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix = "azuredatalakeanalytics.net";
+
+ public const string AzureDataLakeStoreFileSystemEndpointSuffix = "azuredatalakestore.net";
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureEnvironment.cs b/src/Common/Commands.Common.Authentication/Models/AzureEnvironment.cs
new file mode 100644
index 000000000000..8d4da201f47b
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureEnvironment.cs
@@ -0,0 +1,34 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ [Serializable]
+ public partial class AzureEnvironment
+ {
+ public AzureEnvironment()
+ {
+ Endpoints = new Dictionary();
+ }
+
+ public string Name { get; set; }
+
+ public bool OnPremise { get; set; }
+
+ public Dictionary Endpoints { get; set; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureRMProfile.cs b/src/Common/Commands.Common.Authentication/Models/AzureRMProfile.cs
new file mode 100644
index 000000000000..0fbfeb878d60
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureRMProfile.cs
@@ -0,0 +1,147 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ ///
+ /// Represents Azure Resource Manager profile structure with default context, environments and token cache.
+ ///
+ [Serializable]
+ public sealed class AzureRMProfile : IAzureProfile
+ {
+ ///
+ /// Gets or sets Azure environments.
+ ///
+ public Dictionary Environments { get; set; }
+
+ ///
+ /// Gets or sets the default azure context object.
+ ///
+ public AzureContext Context { get; set; }
+
+ ///
+ /// Gets the path of the profile file.
+ ///
+ [JsonIgnore]
+ public string ProfilePath { get; private set; }
+
+ private void Load(string path)
+ {
+ this.ProfilePath = path;
+
+ if (!AzureSession.DataStore.DirectoryExists(AzureSession.ProfileDirectory))
+ {
+ AzureSession.DataStore.CreateDirectory(AzureSession.ProfileDirectory);
+ }
+
+ if (AzureSession.DataStore.FileExists(ProfilePath))
+ {
+ string contents = AzureSession.DataStore.ReadFileAsText(ProfilePath);
+ AzureRMProfile profile = JsonConvert.DeserializeObject(contents);
+ Debug.Assert(profile != null);
+ this.Context = profile.Context;
+ this.Environments = profile.Environments;
+ }
+ }
+
+ ///
+ /// Creates new instance of AzureRMProfile.
+ ///
+ public AzureRMProfile()
+ {
+ Environments = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ // Adding predefined environments
+ foreach (AzureEnvironment env in AzureEnvironment.PublicEnvironments.Values)
+ {
+ Environments[env.Name] = env;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of AzureRMProfile and loads its content from specified path.
+ ///
+ /// The location of profile file on disk.
+ public AzureRMProfile(string path) : this()
+ {
+ Load(path);
+ }
+
+ ///
+ /// Writes profile to the disk it was opened from disk.
+ ///
+ public void Save()
+ {
+ if (!string.IsNullOrEmpty(ProfilePath))
+ {
+ Save(ProfilePath);
+ }
+ }
+
+ ///
+ /// Writes profile to a specified path.
+ ///
+ /// File path on disk to save profile to
+ public void Save(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return;
+ }
+
+ // Removing predefined environments
+ foreach (string env in AzureEnvironment.PublicEnvironments.Keys)
+ {
+ Environments.Remove(env);
+ }
+
+ try
+ {
+ string contents = ToString();
+ string diskContents = string.Empty;
+ if (AzureSession.DataStore.FileExists(path))
+ {
+ diskContents = AzureSession.DataStore.ReadFileAsText(path);
+ }
+
+ if (diskContents != contents)
+ {
+ AzureSession.DataStore.WriteFile(path, contents);
+ }
+ }
+ finally
+ {
+ // Adding back predefined environments
+ foreach (AzureEnvironment env in AzureEnvironment.PublicEnvironments.Values)
+ {
+ Environments[env.Name] = env;
+ }
+ }
+ }
+
+ ///
+ /// Serializes the current profile and return its contents.
+ ///
+ /// The current string.
+ public override string ToString()
+ {
+ return JsonConvert.SerializeObject(this, Formatting.Indented);
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureSMProfile.cs b/src/Common/Commands.Common.Authentication/Models/AzureSMProfile.cs
new file mode 100644
index 000000000000..6d356de3f673
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureSMProfile.cs
@@ -0,0 +1,240 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Hyak.Common;
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ ///
+ /// Represents Azure profile structure with multiple environments, subscriptions, and accounts.
+ ///
+ [Serializable]
+ public sealed class AzureSMProfile : IAzureProfile
+ {
+ ///
+ /// Gets Azure Accounts
+ ///
+ public Dictionary Accounts { get; set; }
+
+ ///
+ /// Gets Azure Subscriptions
+ ///
+ public Dictionary Subscriptions { get; set; }
+
+ ///
+ /// Gets or sets current Azure Subscription
+ ///
+ public AzureSubscription DefaultSubscription
+ {
+ get
+ {
+ return Subscriptions.Values.FirstOrDefault(
+ s => s.Properties.ContainsKey(AzureSubscription.Property.Default));
+ }
+
+ set
+ {
+ if (value == null)
+ {
+ foreach (var subscription in Subscriptions.Values)
+ {
+ subscription.SetProperty(AzureSubscription.Property.Default, null);
+ }
+ }
+ else if (Subscriptions.ContainsKey(value.Id))
+ {
+ foreach (var subscription in Subscriptions.Values)
+ {
+ subscription.SetProperty(AzureSubscription.Property.Default, null);
+ }
+
+ Subscriptions[value.Id].Properties[AzureSubscription.Property.Default] = "True";
+ value.Properties[AzureSubscription.Property.Default] = "True";
+ }
+ }
+ }
+
+ ///
+ /// Gets Azure Environments
+ ///
+ public Dictionary Environments { get; set; }
+
+ ///
+ /// Gets the default azure context object.
+ ///
+ [JsonIgnore]
+ public AzureContext Context
+ {
+ get
+ {
+ var context = new AzureContext(null, null, null, null);
+
+ if (DefaultSubscription != null)
+ {
+ AzureAccount account = null;
+ AzureEnvironment environment = AzureEnvironment.PublicEnvironments[EnvironmentName.AzureCloud];
+ if (DefaultSubscription.Account != null &&
+ Accounts.ContainsKey(DefaultSubscription.Account))
+ {
+ account = Accounts[DefaultSubscription.Account];
+ }
+ else
+ {
+ TracingAdapter.Information(Resources.NoAccountInContext, DefaultSubscription.Account, DefaultSubscription.Id);
+ }
+
+ if (DefaultSubscription.Environment != null &&
+ Environments.ContainsKey(DefaultSubscription.Environment))
+ {
+ environment = Environments[DefaultSubscription.Environment];
+ }
+ else
+ {
+ TracingAdapter.Information(Resources.NoEnvironmentInContext, DefaultSubscription.Environment, DefaultSubscription.Id);
+ }
+
+ context = new AzureContext(DefaultSubscription, account, environment);
+ }
+
+ return context;
+ }
+ }
+
+ ///
+ /// Gets errors from loading the profile.
+ ///
+ public List ProfileLoadErrors { get; private set; }
+
+ ///
+ /// Location of the profile file.
+ ///
+ public string ProfilePath { get; private set; }
+
+ ///
+ /// Initializes a new instance of AzureSMProfile
+ ///
+ public AzureSMProfile()
+ {
+ Environments = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+ Subscriptions = new Dictionary();
+ Accounts = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ // Adding predefined environments
+ foreach (AzureEnvironment env in AzureEnvironment.PublicEnvironments.Values)
+ {
+ Environments[env.Name] = env;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of AzureSMProfile and loads its content from specified path.
+ /// Any errors generated in the process are stored in ProfileLoadErrors collection.
+ ///
+ /// Location of profile file on disk.
+ public AzureSMProfile(string path) : this()
+ {
+ ProfilePath = path;
+ ProfileLoadErrors = new List();
+
+ if (!AzureSession.DataStore.DirectoryExists(AzureSession.ProfileDirectory))
+ {
+ AzureSession.DataStore.CreateDirectory(AzureSession.ProfileDirectory);
+ }
+
+ if (AzureSession.DataStore.FileExists(ProfilePath))
+ {
+ string contents = AzureSession.DataStore.ReadFileAsText(ProfilePath);
+
+ IProfileSerializer serializer;
+
+ if (CloudException.IsXml(contents))
+ {
+ serializer = new XmlProfileSerializer();
+ if (!serializer.Deserialize(contents, this))
+ {
+ ProfileLoadErrors.AddRange(serializer.DeserializeErrors);
+ }
+ }
+ else if (CloudException.IsJson(contents))
+ {
+ serializer = new JsonProfileSerializer();
+ if (!serializer.Deserialize(contents, this))
+ {
+ ProfileLoadErrors.AddRange(serializer.DeserializeErrors);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Writes profile to a ProfilePath
+ ///
+ public void Save()
+ {
+ Save(ProfilePath);
+ }
+
+ ///
+ /// Writes profile to a specified path.
+ ///
+ /// File path on disk to save profile to
+ public void Save(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ return;
+ }
+
+ // Removing predefined environments
+ foreach (string env in AzureEnvironment.PublicEnvironments.Keys)
+ {
+ Environments.Remove(env);
+ }
+
+ try
+ {
+ string contents = ToString();
+ string diskContents = string.Empty;
+ if (AzureSession.DataStore.FileExists(path))
+ {
+ diskContents = AzureSession.DataStore.ReadFileAsText(path);
+ }
+
+ if (diskContents != contents)
+ {
+ AzureSession.DataStore.WriteFile(path, contents);
+ }
+ }
+ finally
+ {
+ // Adding back predefined environments
+ foreach (AzureEnvironment env in AzureEnvironment.PublicEnvironments.Values)
+ {
+ Environments[env.Name] = env;
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ JsonProfileSerializer jsonSerializer = new JsonProfileSerializer();
+ return jsonSerializer.Serialize(this);
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureSubscription.Methods.cs b/src/Common/Commands.Common.Authentication/Models/AzureSubscription.Methods.cs
new file mode 100644
index 000000000000..72e9df3c4386
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureSubscription.Methods.cs
@@ -0,0 +1,70 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Utilities;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ public partial class AzureSubscription
+ {
+ public AzureSubscription()
+ {
+ Properties = new Dictionary();
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+
+ public string GetProperty(Property property)
+ {
+ return Properties.GetProperty(property);
+ }
+
+ public string[] GetPropertyAsArray(Property property)
+ {
+ return Properties.GetPropertyAsArray(property);
+ }
+
+ public void SetProperty(Property property, params string[] values)
+ {
+ Properties.SetProperty(property, values);
+ }
+
+ public void SetOrAppendProperty(Property property, params string[] values)
+ {
+ Properties.SetOrAppendProperty(property, values);
+ }
+
+ public bool IsPropertySet(Property property)
+ {
+ return Properties.IsPropertySet(property);
+ }
+
+ public override bool Equals(object obj)
+ {
+ var anotherSubscription = obj as AzureSubscription;
+ if (anotherSubscription == null)
+ {
+ return false;
+ }
+ else
+ {
+ return anotherSubscription.Id == Id;
+ }
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureSubscription.cs b/src/Common/Commands.Common.Authentication/Models/AzureSubscription.cs
new file mode 100644
index 000000000000..2ae95d2284f8
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureSubscription.cs
@@ -0,0 +1,55 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ [Serializable]
+ public partial class AzureSubscription
+ {
+ public Guid Id { get; set; }
+
+ public string Name { get; set; }
+
+ public string Environment { get; set; }
+
+ public string Account { get; set; }
+
+ public string State { get; set; }
+
+ public Dictionary Properties { get; set; }
+
+ public enum Property
+ {
+ ///
+ /// Comma separated registered resource providers, i.e.: websites,compute,hdinsight
+ ///
+ RegisteredResourceProviders,
+
+ ///
+ /// Associated tenants
+ ///
+ Tenants,
+
+ ///
+ /// If this property existed on the subscription indicates that it's default one.
+ ///
+ Default,
+
+ StorageAccount
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/AzureTenant.cs b/src/Common/Commands.Common.Authentication/Models/AzureTenant.cs
new file mode 100644
index 000000000000..f02c0ac8777f
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/AzureTenant.cs
@@ -0,0 +1,35 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ ///
+ /// Represents an AD tenant.
+ ///
+ [Serializable]
+ public class AzureTenant
+ {
+ ///
+ /// Gets or sets the tenant id.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// Gets or sets the tenant domain.
+ ///
+ public string Domain { get; set; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/DiskDataStore.cs b/src/Common/Commands.Common.Authentication/Models/DiskDataStore.cs
new file mode 100644
index 000000000000..cf1f15302307
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/DiskDataStore.cs
@@ -0,0 +1,180 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using System;
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ public class DiskDataStore : IDataStore
+ {
+ public void WriteFile(string path, string contents)
+ {
+ File.WriteAllText(path, contents);
+ }
+
+ public void WriteFile(string path, string contents, Encoding encoding)
+ {
+ File.WriteAllText(path, contents, encoding);
+ }
+
+ public void WriteFile(string path, byte[] contents)
+ {
+ File.WriteAllBytes(path, contents);
+ }
+
+ public string ReadFileAsText(string path)
+ {
+ return File.ReadAllText(path);
+ }
+
+ public byte[] ReadFileAsBytes(string path)
+ {
+ return File.ReadAllBytes(path);
+ }
+
+ public Stream ReadFileAsStream(string path)
+ {
+ return File.Open(path, FileMode.Open, FileAccess.Read);
+ }
+
+ public void RenameFile(string oldPath, string newPath)
+ {
+ File.Move(oldPath, newPath);
+ }
+
+ public void CopyFile(string oldPath, string newPath)
+ {
+ File.Copy(oldPath, newPath, true);
+ }
+
+ public bool FileExists(string path)
+ {
+ return File.Exists(path);
+ }
+
+ public void DeleteFile(string path)
+ {
+ File.Delete(path);
+ }
+
+ public void DeleteDirectory(string dir)
+ {
+ Directory.Delete(dir, true);
+ }
+
+ public void EmptyDirectory(string dirPath)
+ {
+ foreach (var filePath in Directory.GetFiles(dirPath))
+ {
+ File.Delete(filePath);
+ }
+ }
+
+ public string[] GetFiles(string sourceDirName)
+ {
+ return Directory.GetFiles(sourceDirName);
+ }
+
+ public string[] GetFiles(string startDirectory, string filePattern, SearchOption options)
+ {
+ return Directory.GetFiles(startDirectory, filePattern, options);
+ }
+
+ public FileAttributes GetFileAttributes(string path)
+ {
+ return File.GetAttributes(path);
+ }
+
+ public X509Certificate2 GetCertificate(string thumbprint)
+ {
+ if (thumbprint == null)
+ {
+ return null;
+ }
+ else
+ {
+ Validate.ValidateStringIsNullOrEmpty(thumbprint, "certificate thumbprint");
+ X509Certificate2Collection certificates;
+ if (TryFindCertificatesInStore(thumbprint, StoreLocation.CurrentUser, out certificates) ||
+ TryFindCertificatesInStore(thumbprint, StoreLocation.LocalMachine, out certificates))
+ {
+ return certificates[0];
+ }
+ else
+ {
+ throw new ArgumentException(string.Format(Resources.CertificateNotFoundInStore, thumbprint));
+ }
+ }
+ }
+
+ private static bool TryFindCertificatesInStore(string thumbprint,
+ StoreLocation location, out X509Certificate2Collection certificates)
+ {
+ X509Store store = new X509Store(StoreName.My, location);
+ store.Open(OpenFlags.ReadOnly);
+ certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
+ store.Close();
+
+ return certificates.Count > 0;
+ }
+
+ public void AddCertificate(X509Certificate2 certificate)
+ {
+ Validate.ValidateNullArgument(certificate, Resources.InvalidCertificate);
+ X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
+ store.Open(OpenFlags.ReadWrite);
+ store.Add(certificate);
+ store.Close();
+ }
+
+ public void RemoveCertificate(string thumbprint)
+ {
+ if (thumbprint != null)
+ {
+ var certificate = GetCertificate(thumbprint);
+ if (certificate != null)
+ {
+ X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
+ store.Open(OpenFlags.ReadWrite);
+ store.Remove(certificate);
+ store.Close();
+ }
+ }
+ }
+
+ public bool DirectoryExists(string path)
+ {
+ return Directory.Exists(path);
+ }
+
+ public void CreateDirectory(string path)
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ public string[] GetDirectories(string sourceDirName)
+ {
+ return Directory.GetDirectories(sourceDirName);
+ }
+
+ public string[] GetDirectories(string startDirectory, string filePattern, SearchOption options)
+ {
+ return Directory.GetDirectories(startDirectory, filePattern, options);
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/IAzureProfile.cs b/src/Common/Commands.Common.Authentication/Models/IAzureProfile.cs
new file mode 100644
index 000000000000..bdb1784d8881
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/IAzureProfile.cs
@@ -0,0 +1,27 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ ///
+ /// Interface for Azure supported profiles.
+ ///
+ public interface IAzureProfile
+ {
+ ///
+ /// Gets the default azure context object.
+ ///
+ AzureContext Context { get; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/IClientAction.cs b/src/Common/Commands.Common.Authentication/Models/IClientAction.cs
new file mode 100644
index 000000000000..eea6e424d270
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/IClientAction.cs
@@ -0,0 +1,27 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Hyak.Common;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ public interface IClientAction
+ {
+ IClientFactory ClientFactory { get; set; }
+
+ void Apply(TClient client, AzureSMProfile profile, AzureEnvironment.Endpoint endpoint) where TClient : ServiceClient;
+
+ void ApplyArm(TClient client, AzureRMProfile profile, AzureEnvironment.Endpoint endpoint) where TClient : Microsoft.Rest.ServiceClient;
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/JsonProfileSerializer.cs b/src/Common/Commands.Common.Authentication/Models/JsonProfileSerializer.cs
new file mode 100644
index 000000000000..3510ea1e5a54
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/JsonProfileSerializer.cs
@@ -0,0 +1,91 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ public class JsonProfileSerializer : IProfileSerializer
+ {
+ public string Serialize(AzureSMProfile profile)
+ {
+ return JsonConvert.SerializeObject(new
+ {
+ Environments = profile.Environments.Values.ToList(),
+ Subscriptions = profile.Subscriptions.Values.ToList(),
+ Accounts = profile.Accounts.Values.ToList()
+ }, Formatting.Indented);
+ }
+
+ public bool Deserialize(string contents, AzureSMProfile profile)
+ {
+ DeserializeErrors = new List();
+
+ try
+ {
+ var jsonProfile = JObject.Parse(contents);
+
+ foreach (var env in jsonProfile["Environments"])
+ {
+ try
+ {
+ profile.Environments[(string) env["Name"]] =
+ JsonConvert.DeserializeObject(env.ToString());
+ }
+ catch (Exception ex)
+ {
+ DeserializeErrors.Add(ex.Message);
+ }
+ }
+
+ foreach (var subscription in jsonProfile["Subscriptions"])
+ {
+ try
+ {
+ profile.Subscriptions[new Guid((string) subscription["Id"])] =
+ JsonConvert.DeserializeObject(subscription.ToString());
+ }
+ catch (Exception ex)
+ {
+ DeserializeErrors.Add(ex.Message);
+ }
+ }
+
+ foreach (var account in jsonProfile["Accounts"])
+ {
+ try
+ {
+ profile.Accounts[(string) account["Id"]] =
+ JsonConvert.DeserializeObject(account.ToString());
+ }
+ catch (Exception ex)
+ {
+ DeserializeErrors.Add(ex.Message);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ DeserializeErrors.Add(ex.Message);
+ }
+ return DeserializeErrors.Count == 0;
+ }
+
+ public IList DeserializeErrors { get; private set; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/MemoryDataStore.cs b/src/Common/Commands.Common.Authentication/Models/MemoryDataStore.cs
new file mode 100644
index 000000000000..e9b1b6892a1e
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/MemoryDataStore.cs
@@ -0,0 +1,317 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ public class MemoryDataStore : IDataStore
+ {
+ private Dictionary virtualStore = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+ private Dictionary certStore = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+ private const string FolderKey = "Folder";
+
+ public Dictionary VirtualStore
+ {
+ get { return virtualStore; }
+ set { virtualStore = value; }
+ }
+
+ public void WriteFile(string path, string contents)
+ {
+ VirtualStore[path] = contents;
+ }
+
+ public void WriteFile(string path, string contents, Encoding encoding)
+ {
+ WriteFile(path, contents);
+ }
+
+ public void WriteFile(string path, byte[] contents)
+ {
+ VirtualStore[path] = Encoding.Default.GetString(contents);
+ }
+
+ public string ReadFileAsText(string path)
+ {
+ if (VirtualStore.ContainsKey(path))
+ {
+ return VirtualStore[path];
+ }
+ else
+ {
+ throw new IOException("File not found: " + path);
+ }
+ }
+
+ public Stream ReadFileAsStream(string path)
+ {
+ if (VirtualStore.ContainsKey(path))
+ {
+ MemoryStream stream = new MemoryStream();
+ StreamWriter writer = new StreamWriter(stream);
+ writer.Write(VirtualStore[path]);
+ writer.Flush();
+ stream.Position = 0;
+ return stream;
+ }
+ else
+ {
+ throw new IOException("File not found: " + path);
+ }
+ }
+
+ public byte[] ReadFileAsBytes(string path)
+ {
+ if (VirtualStore.ContainsKey(path))
+ {
+ return Encoding.Default.GetBytes(VirtualStore[path]);
+ }
+ else
+ {
+ throw new IOException("File not found: " + path);
+ }
+ }
+
+ public void RenameFile(string oldPath, string newPath)
+ {
+ if (VirtualStore.ContainsKey(oldPath))
+ {
+ VirtualStore[newPath] = VirtualStore[oldPath];
+ VirtualStore.Remove(oldPath);
+ }
+ else
+ {
+ throw new IOException("File not found: " + oldPath);
+ }
+ }
+
+ public void CopyFile(string oldPath, string newPath)
+ {
+ if (VirtualStore.ContainsKey(oldPath))
+ {
+ VirtualStore[newPath] = VirtualStore[oldPath];
+ }
+ else
+ {
+ throw new IOException("File not found: " + oldPath);
+ }
+ }
+
+ public bool FileExists(string path)
+ {
+ return VirtualStore.ContainsKey(path);
+ }
+
+ public void DeleteFile(string path)
+ {
+ if (VirtualStore.ContainsKey(path))
+ {
+ VirtualStore.Remove(path);
+ }
+ else
+ {
+ throw new IOException("File not found: " + path);
+ }
+ }
+
+ public void DeleteDirectory(string dir)
+ {
+ foreach (var key in VirtualStore.Keys.ToArray())
+ {
+ if (key.StartsWith(dir))
+ {
+ VirtualStore.Remove(key);
+ }
+ }
+ }
+
+ public void EmptyDirectory(string dirPath)
+ {
+ foreach (var key in VirtualStore.Keys.ToArray())
+ {
+ if (key.StartsWith(dirPath))
+ {
+ VirtualStore.Remove(key);
+ }
+ }
+ }
+
+ public bool DirectoryExists(string path)
+ {
+ foreach (var key in VirtualStore.Keys.ToArray())
+ {
+ if (key.StartsWith(path))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void CreateDirectory(string path)
+ {
+ VirtualStore[path] = FolderKey;
+ }
+
+ public string[] GetDirectories(string sourceDirName)
+ {
+ HashSet dirs = new HashSet();
+ foreach (var key in VirtualStore.Keys.ToArray())
+ {
+ if (key.StartsWith(sourceDirName))
+ {
+ var directoryName = Path.GetDirectoryName(key);
+ if (!dirs.Contains(directoryName))
+ {
+ dirs.Add(directoryName);
+ }
+ }
+ }
+ return dirs.ToArray();
+ }
+
+ public string[] GetDirectories(string startDirectory, string filePattern, SearchOption options)
+ {
+ HashSet dirs = new HashSet();
+ foreach (var key in VirtualStore.Keys.ToArray())
+ {
+ if (key.StartsWith(startDirectory) && Regex.IsMatch(key, WildcardToRegex(filePattern), RegexOptions.IgnoreCase))
+ {
+ var directoryName = Path.GetDirectoryName(key);
+ if (!dirs.Contains(directoryName))
+ {
+ dirs.Add(directoryName);
+ }
+ }
+ }
+ return dirs.ToArray();
+ }
+
+ public string[] GetFiles(string sourceDirName)
+ {
+ HashSet files = new HashSet();
+ foreach (var key in VirtualStore.Keys.ToArray())
+ {
+ if (key.StartsWith(sourceDirName) && VirtualStore[key] != FolderKey)
+ {
+ if (!files.Contains(key))
+ {
+ files.Add(key);
+ }
+ }
+ }
+ return files.ToArray();
+ }
+
+ public string[] GetFiles(string startDirectory, string filePattern, SearchOption options)
+ {
+ HashSet files = new HashSet();
+ foreach (var key in VirtualStore.Keys.ToArray())
+ {
+ if (key.StartsWith(startDirectory) && VirtualStore[key] != FolderKey && Regex.IsMatch(key, WildcardToRegex(filePattern), RegexOptions.IgnoreCase))
+ {
+ if (!files.Contains(key))
+ {
+ files.Add(key);
+ }
+ }
+ }
+ return files.ToArray();
+ }
+
+ public FileAttributes GetFileAttributes(string path)
+ {
+ if (VirtualStore[path] == FolderKey)
+ {
+ return FileAttributes.Directory;
+ }
+ if (VirtualStore.ContainsKey(path))
+ {
+ return FileAttributes.Normal;
+ }
+ else
+ {
+ foreach (var key in VirtualStore.Keys.ToArray())
+ {
+ if (key.StartsWith(path))
+ {
+ return FileAttributes.Directory;
+ }
+ }
+ throw new IOException("File not found: " + path);
+ }
+ }
+
+ public X509Certificate2 GetCertificate(string thumbprint)
+ {
+ if (thumbprint != null && certStore.ContainsKey(thumbprint))
+ {
+ return certStore[thumbprint];
+ }
+ else
+ {
+ return new X509Certificate2();
+ }
+ }
+
+ public void AddCertificate(X509Certificate2 cert)
+ {
+ if (cert != null && cert.Thumbprint != null)
+ {
+ certStore[cert.Thumbprint] = cert;
+ }
+ }
+
+ public void RemoveCertificate(string thumbprint)
+ {
+ if (thumbprint != null && certStore.ContainsKey(thumbprint))
+ {
+ certStore.Remove(thumbprint);
+ }
+ }
+
+ ///
+ /// Converts unix asterisk based file pattern to regex
+ ///
+ /// Asterisk based pattern
+ /// Regeular expression of null is empty
+ private static string WildcardToRegex(string wildcard)
+ {
+ if (wildcard == null || wildcard == "") return wildcard;
+
+ StringBuilder sb = new StringBuilder();
+
+ char[] chars = wildcard.ToCharArray();
+ for (int i = 0; i < chars.Length; ++i)
+ {
+ if (chars[i] == '*')
+ sb.Append(".*");
+ else if (chars[i] == '?')
+ sb.Append(".");
+ else if ("+()^$.{}|\\".IndexOf(chars[i]) != -1)
+ sb.Append('\\').Append(chars[i]); // prefix all metacharacters with backslash
+ else
+ sb.Append(chars[i]);
+ }
+ return sb.ToString().ToLowerInvariant();
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Models/XmlProfileSerializer.cs b/src/Common/Commands.Common.Authentication/Models/XmlProfileSerializer.cs
new file mode 100644
index 000000000000..c1eda20aefef
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Models/XmlProfileSerializer.cs
@@ -0,0 +1,95 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Models
+{
+ public class XmlProfileSerializer : IProfileSerializer
+ {
+ public string Serialize(AzureSMProfile obj)
+ {
+ // We do not use the serialize for xml serializer anymore and rely solely on the JSON serializer.
+ throw new NotImplementedException();
+ }
+
+ public bool Deserialize(string contents, AzureSMProfile profile)
+ {
+ ProfileData data;
+ Debug.Assert(profile != null);
+
+ DeserializeErrors = new List();
+
+ DataContractSerializer serializer = new DataContractSerializer(typeof(ProfileData));
+ using (MemoryStream s = new MemoryStream(Encoding.UTF8.GetBytes(contents ?? "")))
+ {
+ data = (ProfileData)serializer.ReadObject(s);
+ }
+
+ if (data != null)
+ {
+ foreach (AzureEnvironmentData oldEnv in data.Environments)
+ {
+ profile.Environments[oldEnv.Name] = oldEnv.ToAzureEnvironment();
+ }
+
+ List envs = profile.Environments.Values.ToList();
+ foreach (AzureSubscriptionData oldSubscription in data.Subscriptions)
+ {
+ try
+ {
+ var newSubscription = oldSubscription.ToAzureSubscription(envs);
+ if (newSubscription.Account == null)
+ {
+ continue;
+ }
+
+ var newAccounts = oldSubscription.ToAzureAccounts();
+ foreach (var account in newAccounts)
+ {
+ if (profile.Accounts.ContainsKey(account.Id))
+ {
+ profile.Accounts[account.Id].SetOrAppendProperty(AzureAccount.Property.Tenants,
+ account.GetPropertyAsArray(AzureAccount.Property.Tenants));
+ profile.Accounts[account.Id].SetOrAppendProperty(AzureAccount.Property.Subscriptions,
+ account.GetPropertyAsArray(AzureAccount.Property.Subscriptions));
+ }
+ else
+ {
+ profile.Accounts[account.Id] = account;
+ }
+ }
+
+ profile.Subscriptions[newSubscription.Id] = newSubscription;
+ }
+ catch (Exception ex)
+ {
+ // Skip subscription if failed to load
+ DeserializeErrors.Add(ex.Message);
+ }
+ }
+ }
+
+ return DeserializeErrors.Count == 0;
+ }
+
+ public IList DeserializeErrors { get; private set; }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Properties/AssemblyInfo.cs b/src/Common/Commands.Common.Authentication/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000000..af2f483fb0a1
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Properties/AssemblyInfo.cs
@@ -0,0 +1,50 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Commands.Common.Authentication")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Commands.Common.Authentication")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("51ee5716-6b2e-4488-8c7e-97e49e9101b8")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Common/Commands.Common.Authentication/Properties/Resources.Designer.cs b/src/Common/Commands.Common.Authentication/Properties/Resources.Designer.cs
new file mode 100644
index 000000000000..abe8955e9c48
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Properties/Resources.Designer.cs
@@ -0,0 +1,612 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Azure.Commands.Common.Authentication.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Account needs to be specified.
+ ///
+ public static string AccountNeedsToBeSpecified {
+ get {
+ return ResourceManager.GetString("AccountNeedsToBeSpecified", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No account was found for this subscription. Please execute Clear-AzureProfile and then execute Add-AzureAccount..
+ ///
+ public static string AccountNotFound {
+ get {
+ return ResourceManager.GetString("AccountNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Authenticating using configuration values: Domain: '{0}', Endpoint: '{1}', ClientId: '{2}', ClientRedirect: '{3}', ResourceClientUri: '{4}', ValidateAuthrity: '{5}'.
+ ///
+ public static string AdalAuthConfigurationTrace {
+ get {
+ return ResourceManager.GetString("AdalAuthConfigurationTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Received exception {0}, while authenticating..
+ ///
+ public static string AdalAuthException {
+ get {
+ return ResourceManager.GetString("AdalAuthException", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Multiple tokens were found for this user. Please clear your token cache using, Clear-AzureProfile and try this command again..
+ ///
+ public static string AdalMultipleTokens {
+ get {
+ return ResourceManager.GetString("AdalMultipleTokens", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User Interaction is required to authenticate this user. Please authenticate using the log in dialog. In PowerShell, execute Login-AzureRMAccount for Azure Resource Manager cmdlets or Add-AzureAccount for service management cmdlets..
+ ///
+ public static string AdalUserInteractionRequired {
+ get {
+ return ResourceManager.GetString("AdalUserInteractionRequired", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No account found in the context. Please login using Login-AzureRMAccount..
+ ///
+ public static string ArmAccountNotFound {
+ get {
+ return ResourceManager.GetString("ArmAccountNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User Interaction is required to authenticate this user. Please execute Login-AzureRMAccount without parameters and enter your credentials..
+ ///
+ public static string ArmUserInteractionRequired {
+ get {
+ return ResourceManager.GetString("ArmUserInteractionRequired", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Authenticating for account {0} with single tenant {1}.
+ ///
+ public static string AuthenticatingForSingleTenant {
+ get {
+ return ResourceManager.GetString("AuthenticatingForSingleTenant", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Windows Azure Powershell.
+ ///
+ public static string AzureDirectoryName {
+ get {
+ return ResourceManager.GetString("AzureDirectoryName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No certificate was found in the certificate store with thumbprint {0}.
+ ///
+ public static string CertificateNotFoundInStore {
+ get {
+ return ResourceManager.GetString("CertificateNotFoundInStore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Changing public environment is not supported..
+ ///
+ public static string ChangingDefaultEnvironmentNotSupported {
+ get {
+ return ResourceManager.GetString("ChangingDefaultEnvironmentNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to -Credential parameter can only be used with Organization ID credentials. For more information, please refer to http://go.microsoft.com/fwlink/?linkid=331007&clcid=0x409 for more information about the difference between an organizational account and a Microsoft account..
+ ///
+ public static string CredentialOrganizationIdMessage {
+ get {
+ return ResourceManager.GetString("CredentialOrganizationIdMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Environment name needs to be specified.
+ ///
+ public static string EnvironmentNameNeedsToBeSpecified {
+ get {
+ return ResourceManager.GetString("EnvironmentNameNeedsToBeSpecified", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Environment needs to be specified.
+ ///
+ public static string EnvironmentNeedsToBeSpecified {
+ get {
+ return ResourceManager.GetString("EnvironmentNeedsToBeSpecified", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The environment name '{0}' is not found..
+ ///
+ public static string EnvironmentNotFound {
+ get {
+ return ResourceManager.GetString("EnvironmentNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Your Microsoft Azure credential in the Windows PowerShell session has expired. Please log in again. In PowerShell, execute Login-AzureRMAccount for Azure Resource Manager cmdlets or Add-AzureAccount for service management cmdlets..
+ ///
+ public static string ExpiredRefreshToken {
+ get {
+ return ResourceManager.GetString("ExpiredRefreshToken", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to File path is not valid.
+ ///
+ public static string FilePathIsNotValid {
+ get {
+ return ResourceManager.GetString("FilePathIsNotValid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Illegal characters in path..
+ ///
+ public static string IllegalPath {
+ get {
+ return ResourceManager.GetString("IllegalPath", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Your Azure credentials have not been set up or have expired, please run Login-AzureRMAccount to set up your Azure credentials..
+ ///
+ public static string InvalidArmContext {
+ get {
+ return ResourceManager.GetString("InvalidArmContext", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Invalid certificate format. Publish settings may be corrupted. Use Get-AzurePublishSettingsFile to download updated settings.
+ ///
+ public static string InvalidCertificate {
+ get {
+ return ResourceManager.GetString("InvalidCertificate", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Credential type invalid, only handles '{0}'.
+ ///
+ public static string InvalidCredentialType {
+ get {
+ return ResourceManager.GetString("InvalidCredentialType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No default subscription has been designated. Use Select-AzureSubscription -Default <subscriptionName> to set the default subscription..
+ ///
+ public static string InvalidDefaultSubscription {
+ get {
+ return ResourceManager.GetString("InvalidDefaultSubscription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to "{0}" is an invalid DNS name for {1}.
+ ///
+ public static string InvalidDnsName {
+ get {
+ return ResourceManager.GetString("InvalidDnsName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The provided file in {0} must be have {1} extension.
+ ///
+ public static string InvalidFileExtension {
+ get {
+ return ResourceManager.GetString("InvalidFileExtension", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot create instance of management client type {0}. It does not have the expected constructor..
+ ///
+ public static string InvalidManagementClientType {
+ get {
+ return ResourceManager.GetString("InvalidManagementClientType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} is invalid or empty.
+ ///
+ public static string InvalidOrEmptyArgumentMessage {
+ get {
+ return ResourceManager.GetString("InvalidOrEmptyArgumentMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Must specify a non-null subscription name..
+ ///
+ public static string InvalidSubscriptionName {
+ get {
+ return ResourceManager.GetString("InvalidSubscriptionName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Your Azure credentials have not been set up or have expired, please run Add-AzureAccount to set up your Azure credentials..
+ ///
+ public static string InvalidSubscriptionState {
+ get {
+ return ResourceManager.GetString("InvalidSubscriptionState", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: No matching account record for account {0} in subscription {1}.
+ ///
+ public static string NoAccountInContext {
+ get {
+ return ResourceManager.GetString("NoAccountInContext", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: No matching environment record for environment {0} in subscription {1}, using AzureCloud environment instead.
+ ///
+ public static string NoEnvironmentInContext {
+ get {
+ return ResourceManager.GetString("NoEnvironmentInContext", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Please connect to internet before executing this cmdlet.
+ ///
+ public static string NoInternetConnection {
+ get {
+ return ResourceManager.GetString("NoInternetConnection", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No subscription found in the context. Please ensure that the credentials you provided are authorized to access an Azure subscription, then run Login-AzureRMAccount to login..
+ ///
+ public static string NoSubscriptionInContext {
+ get {
+ return ResourceManager.GetString("NoSubscriptionInContext", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No tenant found in the context. Please ensure that the credentials you provided are authorized to access an Azure subscription, then run Login-AzureRMAccount to login..
+ ///
+ public static string NoTenantInContext {
+ get {
+ return ResourceManager.GetString("NoTenantInContext", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Path {0} doesn't exist..
+ ///
+ public static string PathDoesNotExist {
+ get {
+ return ResourceManager.GetString("PathDoesNotExist", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Path for {0} doesn't exist in {1}..
+ ///
+ public static string PathDoesNotExistForElement {
+ get {
+ return ResourceManager.GetString("PathDoesNotExistForElement", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to &whr={0}.
+ ///
+ public static string PublishSettingsFileRealmFormat {
+ get {
+ return ResourceManager.GetString("PublishSettingsFileRealmFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Removing public environment is not supported..
+ ///
+ public static string RemovingDefaultEnvironmentsNotSupported {
+ get {
+ return ResourceManager.GetString("RemovingDefaultEnvironmentsNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unable to retrieve service key for ServicePrincipal account {0}. Please log in again to supply the credentials for this service principal. In PowerShell, execute Login-AzureRMAccount for Azure Resource Manager cmdlets or Add-AzureAccount for service management cmdlets..
+ ///
+ public static string ServiceKeyNotFound {
+ get {
+ return ResourceManager.GetString("ServiceKeyNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The provided service name {0} already exists, please pick another name.
+ ///
+ public static string ServiceNameExists {
+ get {
+ return ResourceManager.GetString("ServiceNameExists", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Renewing token using AppId: '{0}', AdalConfiguration with ADDomain: '{1}', AdEndpoint: '{2}', ClientId: '{3}', RedirectUri: '{4}'.
+ ///
+ public static string SPNRenewTokenTrace {
+ get {
+ return ResourceManager.GetString("SPNRenewTokenTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Checking token expiration, token expires '{0}' Comparing to '{1}' With threshold '{2}', calculated time until token expiry: '{3}'.
+ ///
+ public static string SPNTokenExpirationCheckTrace {
+ get {
+ return ResourceManager.GetString("SPNTokenExpirationCheckTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The subscription id {0} doesn't exist..
+ ///
+ public static string SubscriptionIdNotFoundMessage {
+ get {
+ return ResourceManager.GetString("SubscriptionIdNotFoundMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Subscription name needs to be specified.
+ ///
+ public static string SubscriptionNameNeedsToBeSpecified {
+ get {
+ return ResourceManager.GetString("SubscriptionNameNeedsToBeSpecified", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The subscription name {0} doesn't exist..
+ ///
+ public static string SubscriptionNameNotFoundMessage {
+ get {
+ return ResourceManager.GetString("SubscriptionNameNotFoundMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Subscription needs to be specified.
+ ///
+ public static string SubscriptionNeedsToBeSpecified {
+ get {
+ return ResourceManager.GetString("SubscriptionNeedsToBeSpecified", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No tenant was found for this subscription. Please execute Clear-AzureProfile and then execute Add-AzureAccount..
+ ///
+ public static string TenantNotFound {
+ get {
+ return ResourceManager.GetString("TenantNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unable to update mismatching Json structured: {0} {1}..
+ ///
+ public static string UnableToPatchJson {
+ get {
+ return ResourceManager.GetString("UnableToPatchJson", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Illegal credential type.
+ ///
+ public static string UnknownCredentialType {
+ get {
+ return ResourceManager.GetString("UnknownCredentialType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Certificate authentication is not supported for account type {0}..
+ ///
+ public static string UnsupportedCredentialType {
+ get {
+ return ResourceManager.GetString("UnsupportedCredentialType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Acquiring token using AdalConfiguration with Domain: '{0}', AdEndpoint: '{1}', ClientId: '{2}', ClientRedirectUri: {3}.
+ ///
+ public static string UPNAcquireTokenConfigTrace {
+ get {
+ return ResourceManager.GetString("UPNAcquireTokenConfigTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Acquiring token using context with Authority '{0}', CorrelationId: '{1}', ValidateAuthority: '{2}'.
+ ///
+ public static string UPNAcquireTokenContextTrace {
+ get {
+ return ResourceManager.GetString("UPNAcquireTokenContextTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Received token with LoginType '{0}', Tenant: '{1}', UserId: '{2}'.
+ ///
+ public static string UPNAuthenticationTokenTrace {
+ get {
+ return ResourceManager.GetString("UPNAuthenticationTokenTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Authenticating using Account: '{0}', environment: '{1}', tenant: '{2}'.
+ ///
+ public static string UPNAuthenticationTrace {
+ get {
+ return ResourceManager.GetString("UPNAuthenticationTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Token is expired.
+ ///
+ public static string UPNExpiredTokenTrace {
+ get {
+ return ResourceManager.GetString("UPNExpiredTokenTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Renewing Token with Type: '{0}', Expiry: '{1}', MultipleResource? '{2}', Tenant: '{3}', UserId: '{4}'.
+ ///
+ public static string UPNRenewTokenTrace {
+ get {
+ return ResourceManager.GetString("UPNRenewTokenTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: User info for token DisplayId: '{0}', Name: {2} {1}, IdProvider: '{3}', Uid: '{4}'.
+ ///
+ public static string UPNRenewTokenUserInfoTrace {
+ get {
+ return ResourceManager.GetString("UPNRenewTokenUserInfoTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to [Common.Authentication]: Checking token expiration, token expires '{0}' Comparing to '{1}' With threshold '{2}', calculated time until token expiry: '{3}'.
+ ///
+ public static string UPNTokenExpirationCheckTrace {
+ get {
+ return ResourceManager.GetString("UPNTokenExpirationCheckTrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User name is not valid.
+ ///
+ public static string UserNameIsNotValid {
+ get {
+ return ResourceManager.GetString("UserNameIsNotValid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to User name needs to be specified.
+ ///
+ public static string UserNameNeedsToBeSpecified {
+ get {
+ return ResourceManager.GetString("UserNameNeedsToBeSpecified", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to (x86).
+ ///
+ public static string x86InProgramFiles {
+ get {
+ return ResourceManager.GetString("x86InProgramFiles", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Properties/Resources.resx b/src/Common/Commands.Common.Authentication/Properties/Resources.resx
new file mode 100644
index 000000000000..40dc18b1e1e5
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Properties/Resources.resx
@@ -0,0 +1,303 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Account needs to be specified
+
+
+ Windows Azure Powershell
+
+
+ No certificate was found in the certificate store with thumbprint {0}
+
+
+ Changing public environment is not supported.
+
+
+ -Credential parameter can only be used with Organization ID credentials. For more information, please refer to http://go.microsoft.com/fwlink/?linkid=331007&clcid=0x409 for more information about the difference between an organizational account and a Microsoft account.
+
+
+ Environment name needs to be specified
+
+
+ Environment needs to be specified
+
+
+ The environment name '{0}' is not found.
+
+
+ Your Microsoft Azure credential in the Windows PowerShell session has expired. Please log in again. In PowerShell, execute Login-AzureRMAccount for Azure Resource Manager cmdlets or Add-AzureAccount for service management cmdlets.
+
+
+ File path is not valid
+
+
+ Illegal characters in path.
+
+
+ Invalid certificate format. Publish settings may be corrupted. Use Get-AzurePublishSettingsFile to download updated settings
+
+
+ Credential type invalid, only handles '{0}'
+
+
+ No default subscription has been designated. Use Select-AzureSubscription -Default <subscriptionName> to set the default subscription.
+
+
+ "{0}" is an invalid DNS name for {1}
+
+
+ The provided file in {0} must be have {1} extension
+
+
+ Cannot create instance of management client type {0}. It does not have the expected constructor.
+
+
+ {0} is invalid or empty
+
+
+ Must specify a non-null subscription name.
+
+
+ Your Azure credentials have not been set up or have expired, please run Add-AzureAccount to set up your Azure credentials.
+
+
+ Please connect to internet before executing this cmdlet
+
+
+ Path {0} doesn't exist.
+
+
+ Path for {0} doesn't exist in {1}.
+
+
+ &whr={0}
+
+
+ Removing public environment is not supported.
+
+
+ Unable to retrieve service key for ServicePrincipal account {0}. Please log in again to supply the credentials for this service principal. In PowerShell, execute Login-AzureRMAccount for Azure Resource Manager cmdlets or Add-AzureAccount for service management cmdlets.
+
+
+ The provided service name {0} already exists, please pick another name
+
+
+ [Common.Authentication]: Renewing token using AppId: '{0}', AdalConfiguration with ADDomain: '{1}', AdEndpoint: '{2}', ClientId: '{3}', RedirectUri: '{4}'
+
+
+ [Common.Authentication]: Checking token expiration, token expires '{0}' Comparing to '{1}' With threshold '{2}', calculated time until token expiry: '{3}'
+
+
+ The subscription id {0} doesn't exist.
+
+
+ Subscription name needs to be specified
+
+
+ The subscription name {0} doesn't exist.
+
+
+ Subscription needs to be specified
+
+
+ Unable to update mismatching Json structured: {0} {1}.
+
+
+ Illegal credential type
+
+
+ [Common.Authentication]: Acquiring token using AdalConfiguration with Domain: '{0}', AdEndpoint: '{1}', ClientId: '{2}', ClientRedirectUri: {3}
+
+
+ [Common.Authentication]: Acquiring token using context with Authority '{0}', CorrelationId: '{1}', ValidateAuthority: '{2}'
+
+
+ [Common.Authentication]: Token is expired
+
+
+ [Common.Authentication]: Renewing Token with Type: '{0}', Expiry: '{1}', MultipleResource? '{2}', Tenant: '{3}', UserId: '{4}'
+
+
+ [Common.Authentication]: User info for token DisplayId: '{0}', Name: {2} {1}, IdProvider: '{3}', Uid: '{4}'
+
+
+ [Common.Authentication]: Checking token expiration, token expires '{0}' Comparing to '{1}' With threshold '{2}', calculated time until token expiry: '{3}'
+
+
+ User name is not valid
+
+
+ User name needs to be specified
+
+
+ (x86)
+
+
+ No account was found for this subscription. Please execute Clear-AzureProfile and then execute Add-AzureAccount.
+
+
+ [Common.Authentication]: Received exception {0}, while authenticating.
+
+
+ Multiple tokens were found for this user. Please clear your token cache using, Clear-AzureProfile and try this command again.
+
+
+ User Interaction is required to authenticate this user. Please authenticate using the log in dialog. In PowerShell, execute Login-AzureRMAccount for Azure Resource Manager cmdlets or Add-AzureAccount for service management cmdlets.
+
+
+ No tenant was found for this subscription. Please execute Clear-AzureProfile and then execute Add-AzureAccount.
+
+
+ [Common.Authentication]: Received token with LoginType '{0}', Tenant: '{1}', UserId: '{2}'
+
+
+ [Common.Authentication]: Authenticating using Account: '{0}', environment: '{1}', tenant: '{2}'
+
+
+ [Common.Authentication]: Authenticating using configuration values: Domain: '{0}', Endpoint: '{1}', ClientId: '{2}', ClientRedirect: '{3}', ResourceClientUri: '{4}', ValidateAuthrity: '{5}'
+
+
+ [Common.Authentication]: No matching account record for account {0} in subscription {1}
+
+
+ [Common.Authentication]: No matching environment record for environment {0} in subscription {1}, using AzureCloud environment instead
+
+
+ [Common.Authentication]: Authenticating for account {0} with single tenant {1}
+
+
+ No account found in the context. Please login using Login-AzureRMAccount.
+
+
+ User Interaction is required to authenticate this user. Please execute Login-AzureRMAccount without parameters and enter your credentials.
+
+
+ Your Azure credentials have not been set up or have expired, please run Login-AzureRMAccount to set up your Azure credentials.
+
+
+ No subscription found in the context. Please ensure that the credentials you provided are authorized to access an Azure subscription, then run Login-AzureRMAccount to login.
+
+
+ No tenant found in the context. Please ensure that the credentials you provided are authorized to access an Azure subscription, then run Login-AzureRMAccount to login.
+
+
+ Certificate authentication is not supported for account type {0}.
+
+
\ No newline at end of file
diff --git a/src/Common/Commands.Common.Authentication/Utilities/DictionaryExtensions.cs b/src/Common/Commands.Common.Authentication/Utilities/DictionaryExtensions.cs
new file mode 100644
index 000000000000..fd279b236a9d
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Utilities/DictionaryExtensions.cs
@@ -0,0 +1,78 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Azure.Commands.Common.Authentication.Utilities
+{
+ public static class DictionaryExtensions
+ {
+ public static TValue GetProperty(this Dictionary dictionary, TKey property)
+ {
+ if (dictionary.ContainsKey(property))
+ {
+ return dictionary[property];
+ }
+
+ return default(TValue);
+ }
+
+ public static string[] GetPropertyAsArray(this Dictionary dictionary, TKey property)
+ {
+ if (dictionary.ContainsKey(property))
+ {
+ return dictionary[property].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ return new string[0];
+ }
+
+ public static void SetProperty(this Dictionary dictionary, TKey property, params string[] values)
+ {
+ if (values == null || values.Length == 0)
+ {
+ if (dictionary.ContainsKey(property))
+ {
+ dictionary.Remove(property);
+ }
+ }
+ else
+ {
+ dictionary[property] = string.Join(",", values);
+ }
+ }
+
+ public static void SetOrAppendProperty(this Dictionary dictionary, TKey property, params string[] values)
+ {
+ string oldValueString = "";
+ if (dictionary.ContainsKey(property))
+ {
+ oldValueString = dictionary[property];
+ }
+ var oldValues = oldValueString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ var newValues = oldValues.Union(values, StringComparer.CurrentCultureIgnoreCase).Where(s => !string.IsNullOrEmpty(s)).ToArray();
+ if (newValues.Any())
+ {
+ dictionary[property] = string.Join(",", newValues);
+ }
+ }
+
+ public static bool IsPropertySet(this Dictionary dictionary, TKey property)
+ {
+ return dictionary.ContainsKey(property) && !string.IsNullOrEmpty(dictionary[property]);
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Utilities/FileUtilities.cs b/src/Common/Commands.Common.Authentication/Utilities/FileUtilities.cs
new file mode 100644
index 000000000000..cb361deaf860
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Utilities/FileUtilities.cs
@@ -0,0 +1,321 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Models;
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public static class FileUtilities
+ {
+ static FileUtilities()
+ {
+ DataStore = new DiskDataStore();
+ }
+
+ public static IDataStore DataStore { get; set; }
+
+ public static string GetAssemblyDirectory()
+ {
+ var assemblyPath = Uri.UnescapeDataString(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath);
+ return Path.GetDirectoryName(assemblyPath);
+ }
+
+ public static string GetContentFilePath(string fileName)
+ {
+ return GetContentFilePath(GetAssemblyDirectory(), fileName);
+ }
+
+ public static string GetContentFilePath(string startDirectory, string fileName)
+ {
+ string path = Path.Combine(startDirectory, fileName);
+
+ // Try search in the subdirectories in case that the file path does not exist in root path
+ if (!DataStore.FileExists(path) && !DataStore.DirectoryExists(path))
+ {
+ try
+ {
+ path = DataStore.GetDirectories(startDirectory, fileName, SearchOption.AllDirectories).FirstOrDefault();
+
+ if (string.IsNullOrEmpty(path))
+ {
+ path = DataStore.GetFiles(startDirectory, fileName, SearchOption.AllDirectories).First();
+ }
+ }
+ catch
+ {
+ throw new FileNotFoundException(Path.Combine(startDirectory, fileName));
+ }
+ }
+
+ return path;
+ }
+
+ public static string GetWithProgramFilesPath(string directoryName, bool throwIfNotFound)
+ {
+ string programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
+ if (DataStore.DirectoryExists(Path.Combine(programFilesPath, directoryName)))
+ {
+ return Path.Combine(programFilesPath, directoryName);
+ }
+ else
+ {
+ if (programFilesPath.IndexOf(Resources.x86InProgramFiles, StringComparison.InvariantCultureIgnoreCase) == -1)
+ {
+ programFilesPath += Resources.x86InProgramFiles;
+ if (throwIfNotFound)
+ {
+ Validate.ValidateDirectoryExists(Path.Combine(programFilesPath, directoryName));
+ }
+ return Path.Combine(programFilesPath, directoryName);
+ }
+ else
+ {
+ programFilesPath = programFilesPath.Replace(Resources.x86InProgramFiles, String.Empty);
+ if (throwIfNotFound)
+ {
+ Validate.ValidateDirectoryExists(Path.Combine(programFilesPath, directoryName));
+ }
+ return Path.Combine(programFilesPath, directoryName);
+ }
+ }
+ }
+
+ ///
+ /// Copies a directory.
+ ///
+ /// The source directory name
+ /// The destination directory name
+ /// Should the copy be recursive
+ public static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
+ {
+ var dirs = DataStore.GetDirectories(sourceDirName);
+
+ if (!DataStore.DirectoryExists(sourceDirName))
+ {
+ throw new DirectoryNotFoundException(String.Format(Resources.PathDoesNotExist, sourceDirName));
+ }
+
+ DataStore.CreateDirectory(destDirName);
+
+ var files = DataStore.GetFiles(sourceDirName);
+ foreach (var file in files)
+ {
+ string tempPath = Path.Combine(destDirName, Path.GetFileName(file));
+ DataStore.CopyFile(file, tempPath);
+ }
+
+ if (copySubDirs)
+ {
+ foreach (var subdir in dirs)
+ {
+ string temppath = Path.Combine(destDirName, Path.GetDirectoryName(subdir));
+ DirectoryCopy(subdir, temppath, copySubDirs);
+ }
+ }
+ }
+
+ ///
+ /// Ensures that a directory exists beofre attempting to write a file
+ ///
+ /// The path to the file that will be created
+ public static void EnsureDirectoryExists(string pathName)
+ {
+ Validate.ValidateStringIsNullOrEmpty(pathName, "Settings directory");
+ string directoryPath = Path.GetDirectoryName(pathName);
+ if (!DataStore.DirectoryExists(directoryPath))
+ {
+ DataStore.CreateDirectory(directoryPath);
+ }
+ }
+
+ ///
+ /// Create a unique temp directory.
+ ///
+ /// Path to the temp directory.
+ public static string CreateTempDirectory()
+ {
+ string tempPath;
+ do
+ {
+ tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+ }
+ while (DataStore.DirectoryExists(tempPath) || DataStore.FileExists(tempPath));
+
+ DataStore.CreateDirectory(tempPath);
+ return tempPath;
+ }
+
+ ///
+ /// Copy a directory from one path to another.
+ ///
+ /// Source directory.
+ /// Destination directory.
+ public static void CopyDirectory(string sourceDirectory, string destinationDirectory)
+ {
+ Debug.Assert(!String.IsNullOrEmpty(sourceDirectory), "sourceDictory cannot be null or empty!");
+ Debug.Assert(Directory.Exists(sourceDirectory), "sourceDirectory must exist!");
+ Debug.Assert(!String.IsNullOrEmpty(destinationDirectory), "destinationDirectory cannot be null or empty!");
+ Debug.Assert(!Directory.Exists(destinationDirectory), "destinationDirectory must not exist!");
+
+ foreach (string file in DataStore.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories))
+ {
+ string relativePath = file.Substring(
+ sourceDirectory.Length + 1,
+ file.Length - sourceDirectory.Length - 1);
+ string destinationPath = Path.Combine(destinationDirectory, relativePath);
+
+ string destinationDir = Path.GetDirectoryName(destinationPath);
+ if (!DataStore.DirectoryExists(destinationDir))
+ {
+ DataStore.CreateDirectory(destinationDir);
+ }
+
+ DataStore.CopyFile(file, destinationPath);
+ }
+ }
+
+ public static Encoding GetFileEncoding(string path)
+ {
+ Encoding encoding;
+
+
+ if (DataStore.FileExists(path))
+ {
+ using (StreamReader r = new StreamReader(DataStore.ReadFileAsStream(path)))
+ {
+ encoding = r.CurrentEncoding;
+ }
+ }
+ else
+ {
+ encoding = Encoding.Default;
+ }
+
+ return encoding;
+ }
+
+ public static string CombinePath(params string[] paths)
+ {
+ return Path.Combine(paths);
+ }
+
+ ///
+ /// Returns true if path is a valid directory.
+ ///
+ ///
+ ///
+ public static bool IsValidDirectoryPath(string path)
+ {
+ if (String.IsNullOrEmpty(path))
+ {
+ return false;
+ }
+
+ try
+ {
+ FileAttributes attributes = DataStore.GetFileAttributes(path);
+
+ if ((attributes & FileAttributes.Directory) == FileAttributes.Directory)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public static void RecreateDirectory(string dir)
+ {
+ if (DataStore.DirectoryExists(dir))
+ {
+ DataStore.DeleteDirectory(dir);
+ }
+
+ DataStore.CreateDirectory(dir);
+ }
+
+ ///
+ /// Gets the root installation path for the given Azure module.
+ ///
+ /// The module name
+ /// The module full path
+ public static string GetPSModulePathForModule(AzureModule module)
+ {
+ return GetContentFilePath(GetInstallPath(), GetModuleFolderName(module));
+ }
+
+ ///
+ /// Gets the root directory for all modules installation.
+ ///
+ /// The install path
+ public static string GetInstallPath()
+ {
+ string currentPath = GetAssemblyDirectory();
+ while (!currentPath.EndsWith(GetModuleFolderName(AzureModule.AzureProfile)) &&
+ !currentPath.EndsWith(GetModuleFolderName(AzureModule.AzureResourceManager)) &&
+ !currentPath.EndsWith(GetModuleFolderName(AzureModule.AzureServiceManagement)))
+ {
+ currentPath = Directory.GetParent(currentPath).FullName;
+ }
+
+ // The assemption is that the install directory looks like that:
+ // ServiceManagement
+ // AzureServiceManagement
+ //
+ // ResourceManager
+ // AzureResourceManager
+ //
+ // Profile
+ // AzureSMProfile
+ //
+ return Directory.GetParent(currentPath).FullName;
+ }
+
+ public static string GetModuleName(AzureModule module)
+ {
+ switch (module)
+ {
+ case AzureModule.AzureServiceManagement:
+ return "Azure";
+
+ case AzureModule.AzureResourceManager:
+ return "AzureResourceManager";
+
+ case AzureModule.AzureProfile:
+ return "AzureProfile";
+
+ default:
+ throw new ArgumentOutOfRangeException(module.ToString());
+ }
+ }
+
+ public static string GetModuleFolderName(AzureModule module)
+ {
+ return module.ToString().Replace("Azure", "");
+ }
+ }
+}
diff --git a/src/Common/Commands.Common.Authentication/Utilities/JsonUtilities.cs b/src/Common/Commands.Common.Authentication/Utilities/JsonUtilities.cs
new file mode 100644
index 000000000000..2149e045b5a0
--- /dev/null
+++ b/src/Common/Commands.Common.Authentication/Utilities/JsonUtilities.cs
@@ -0,0 +1,204 @@
+// ----------------------------------------------------------------------------------
+//
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+using Microsoft.Azure.Commands.Common.Authentication.Properties;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Azure.Commands.Common.Authentication
+{
+ public static class JsonUtilities
+ {
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Handling the failure by returning the original string.")]
+ public static string TryFormatJson(string str)
+ {
+ try
+ {
+ object parsedJson = JsonConvert.DeserializeObject(str);
+ return JsonConvert.SerializeObject(parsedJson, Formatting.Indented);
+ }
+ catch
+ {
+ // can't parse JSON, return the original string
+ return str;
+ }
+ }
+
+ public static Dictionary DeserializeJson(string jsonString, bool throwExceptionOnFailure = false)
+ {
+ Dictionary result = new Dictionary();
+ if (jsonString == null)
+ {
+ return null;
+ }
+ if (String.IsNullOrWhiteSpace(jsonString))
+ {
+ return result;
+ }
+
+ try
+ {
+ JToken responseDoc = JToken.Parse(jsonString);
+
+ if (responseDoc != null && responseDoc.Type == JTokenType.Object)
+ {
+ result = DeserializeJObject(responseDoc as JObject);
+ }
+ }
+ catch
+ {
+ if (throwExceptionOnFailure)
+ {
+ throw;
+ }
+ result = null;
+ }
+ return result;
+ }
+
+ private static Dictionary DeserializeJObject(JObject jsonObject)
+ {
+ Dictionary result = new Dictionary();
+ if (jsonObject == null || jsonObject.Type == JTokenType.Null)
+ {
+ return result;
+ }
+ foreach (var property in jsonObject)
+ {
+ if (property.Value.Type == JTokenType.Object)
+ {
+ result[property.Key] = DeserializeJObject(property.Value as JObject);
+ }
+ else if (property.Value.Type == JTokenType.Array)
+ {
+ result[property.Key] = DeserializeJArray(property.Value as JArray);
+ }
+ else
+ {
+ result[property.Key] = DeserializeJValue(property.Value as JValue);
+ }
+ }
+ return result;
+ }
+
+ private static List