├── .gitignore ├── IAMFox-src.app └── Contents │ ├── Info.plist │ ├── MacOS │ └── xulrunner │ ├── PkgInfo │ └── Resources ├── README ├── Resources ├── IAMFOX.icns ├── application.ini ├── chrome │ ├── chrome.manifest │ ├── content │ │ ├── about-dialog.xul │ │ ├── about.png │ │ ├── account-dialog.xul │ │ ├── account-tree-view.js │ │ ├── error-dialog.xul │ │ ├── exp_imp.js │ │ ├── favicon.png │ │ ├── group-add-dialog.xul │ │ ├── group-detail-window.js │ │ ├── group-detail-window.xul │ │ ├── group-edit-dialog.xul │ │ ├── group-tabpanel.xul │ │ ├── group-tree-view.js │ │ ├── iam-client.js │ │ ├── iamfox.xul │ │ ├── io.js │ │ ├── loader.gif │ │ ├── main.js │ │ ├── main.xul │ │ ├── preferences.js │ │ ├── server-cert-add-window.js │ │ ├── server-cert-add-window.xul │ │ ├── server-cert-edit-dialog.xul │ │ ├── server-cert-tabpanel.xul │ │ ├── server-cert-tree-view.js │ │ ├── sha1.js │ │ ├── signing-cert-add-dialog.xul │ │ ├── signing-cert-edit-dialog.xul │ │ ├── user-add-dialog.xul │ │ ├── user-cert-window.js │ │ ├── user-cert-window.xul │ │ ├── user-create-login-profile-dialog.xul │ │ ├── user-detail-window.js │ │ ├── user-detail-window.xul │ │ ├── user-edit-dialog.xul │ │ ├── user-group-window.js │ │ ├── user-group-window.xul │ │ ├── user-open-console-dialog.xul │ │ ├── user-tabpanel.xul │ │ ├── user-tree-view.js │ │ ├── user-view-key-dialog.xul │ │ ├── user-view-key-tree-view.js │ │ └── utils.js │ ├── icons │ │ └── default │ │ │ └── main-window.ico │ ├── install.rdf │ ├── locale │ │ └── en-US │ │ │ └── iamfox.dtd │ └── skin │ │ └── classic │ │ ├── iamfox.css │ │ └── menuicon.png └── defaults │ └── preferences │ └── prefs.js ├── iam-fox.ci ├── pack-osx.sh ├── pack-win.sh └── pack-xpi.sh /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | .DS_Store 3 | *.dmg 4 | IAMFox.app 5 | iam-fox_win 6 | *setup.exe 7 | *.log 8 | *.xpi 9 | -------------------------------------------------------------------------------- /IAMFox-src.app/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | xulrunner 7 | CFBundleGetInfoString 8 | IAMFox 9 | CFBundleIconFile 10 | IAMFOX.icns 11 | CFBundleIdentifier 12 | xulrunner.iamfox 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | IAMFox 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.3.0 21 | CFBundleSignature 22 | IAMFOX 23 | CFBundleVersion 24 | 0.1.6 25 | NSAppleScriptEnabled 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /IAMFox-src.app/Contents/MacOS/xulrunner: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookpad/iam-fox/675f751976bab0db79766bd03a76218c20122c8c/IAMFox-src.app/Contents/MacOS/xulrunner -------------------------------------------------------------------------------- /IAMFox-src.app/Contents/PkgInfo: -------------------------------------------------------------------------------- 1 | APPLIAMFOX 2 | -------------------------------------------------------------------------------- /IAMFox-src.app/Contents/Resources: -------------------------------------------------------------------------------- 1 | ../../Resources -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | = IAM Fox 2 | 3 | == Description 4 | 5 | IAM Fox is a GUI client of AWS Identity and Access Management (IAM) 6 | 7 | see http://aws.amazon.com/iam/ 8 | 9 | == Requires 10 | 11 | * OSX 12 | * Application bundle contains XULRunner runtime 13 | * If the application doesn't start, please install XULRunner runtime 14 | * http://releases.mozilla.org/pub/mozilla.org/xulrunner/releases/ 15 | * Windows 16 | * Installer contains XULRunner runtime 17 | 18 | == Credit 19 | 20 | * Icon from 'PC 100 Icon Pack' 21 | * http://pc.de/icons/ 22 | -------------------------------------------------------------------------------- /Resources/IAMFOX.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookpad/iam-fox/675f751976bab0db79766bd03a76218c20122c8c/Resources/IAMFOX.icns -------------------------------------------------------------------------------- /Resources/application.ini: -------------------------------------------------------------------------------- 1 | [App] 2 | Vendor=winebarrel 3 | Name=IAM Fox 4 | Version=0.1.6 5 | BuildID=$BuildID: 537c6f9791b882a77d163b68781e979b24048a5f $ 6 | Copyright=Copyright (c) 2011- Genki Sugawara 7 | ID=sgwr_dts@yahoo.co.jp 8 | 9 | [Gecko] 10 | MinVersion=1.8 11 | MaxVersion=1.9.* 12 | -------------------------------------------------------------------------------- /Resources/chrome/chrome.manifest: -------------------------------------------------------------------------------- 1 | content iamfox content/ 2 | locale iamfox en-US locale/en-US/ 3 | skin iamfox classic/1.0 skin/classic/ 4 | overlay chrome://browser/content/browser.xul chrome://iamfox/content/iamfox.xul 5 | -------------------------------------------------------------------------------- /Resources/chrome/content/about-dialog.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 40 | 41 | -------------------------------------------------------------------------------- /Resources/chrome/content/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookpad/iam-fox/675f751976bab0db79766bd03a76218c20122c8c/Resources/chrome/content/about.png -------------------------------------------------------------------------------- /Resources/chrome/content/account-dialog.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 109 | 110 | -------------------------------------------------------------------------------- /Resources/chrome/content/account-tree-view.js: -------------------------------------------------------------------------------- 1 | function AccountTreeView(userNameElement, accessKeyIdElement, secretAccessKeyElement) { 2 | this.userNameElement = userNameElement; 3 | this.accessKeyIdElement = accessKeyIdElement; 4 | this.secretAccessKeyElement = secretAccessKeyElement; 5 | 6 | this.rowCount = 0; 7 | this.selection = null; 8 | this.sorted = false; 9 | this.data = []; 10 | } 11 | 12 | AccountTreeView.prototype = { 13 | updateData: function() { 14 | var accounts = Prefs.getAccountList(); 15 | var data = []; 16 | 17 | for (var i = 0; i < accounts.length; i++) { 18 | var account = accounts[i]; 19 | data.push([account[0], account[1].accessKeyId, account[1].secretAccessKey]); 20 | } 21 | 22 | this.data = data; 23 | }, 24 | 25 | getCellText: function(row, column) { 26 | if (column.id == 'account-tree-use') { 27 | return null; 28 | } 29 | 30 | var accounts = Prefs.getAccountList(); 31 | 32 | var idx = column.id.toString().split('.'); 33 | idx = idx[idx.length - 1]; 34 | 35 | return this.data[row][idx]; 36 | }, 37 | 38 | getCellValue: function(row, column) { 39 | if (column.id != 'account-tree-use') { 40 | return null; 41 | } 42 | 43 | var userName = this.data[row][0]; 44 | var currentUser = Prefs.currentUser; 45 | 46 | return (currentUser && (currentUser == userName)); 47 | }, 48 | 49 | setTree: function(tree) { 50 | this.tree = tree; 51 | }, 52 | 53 | isEditable: function(row, column) { 54 | return (column.id == 'account-tree-use'); 55 | }, 56 | 57 | setCellValue: function(row, column, value) { 58 | if (!value) { return; } 59 | 60 | var userName = this.data[row][0]; 61 | Prefs.currentUser = userName; 62 | this.refresh(); 63 | }, 64 | 65 | refresh: function() { 66 | this.updateData(); 67 | 68 | if (this.rowCount != this.data.length) { 69 | this.tree.rowCountChanged(0, -this.rowCount); 70 | this.rowCount = this.data.length; 71 | this.tree.rowCountChanged(0, this.rowCount); 72 | } 73 | 74 | this.tree.invalidate(); 75 | }, 76 | 77 | selectedRow: function() { 78 | var idx = this.selection.currentIndex; 79 | return (idx != -1) ? this.data[idx] : null; 80 | }, 81 | 82 | copyColumnToClipboard: function(name) { 83 | var row = this.selectedRow(); 84 | 85 | if (row) { 86 | var value = (row[name] || '').toString().trim(); 87 | if (!value) { value = '(empty)'; } 88 | copyToClipboard(value); 89 | } 90 | }, 91 | 92 | addAccount: function(userName, accessKeyId, secretAccessKey) { 93 | Prefs.addAccount(userName, accessKeyId, secretAccessKey); 94 | this.refresh(); 95 | }, 96 | 97 | deleteAccount: function() { 98 | var row = this.selectedRow(); 99 | if (!row) { return; } 100 | 101 | var userName = row[0]; 102 | 103 | if (!confirm("Are you sure you want to delete '" + userName + "' ?")) { 104 | return; 105 | } 106 | 107 | Prefs.deleteAccount(userName); 108 | this.refresh(); 109 | }, 110 | 111 | onDblclick: function(event) { 112 | var row = this.selectedRow(); 113 | 114 | if (!row || (event && event.target.tagName != 'treechildren')) { 115 | return; 116 | } 117 | 118 | this.userNameElement.value = row[0]; 119 | this.accessKeyIdElement.value = row[1]; 120 | this.secretAccessKeyElement.value = row[2]; 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /Resources/chrome/content/error-dialog.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 39 | 40 | -------------------------------------------------------------------------------- /Resources/chrome/content/exp_imp.js: -------------------------------------------------------------------------------- 1 | function exportKeysToJson() { 2 | var fp = newFilePicker(); 3 | fp.init(window, 'Export keys', Components.interfaces.nsIFilePicker.modeSave); 4 | fp.defaultString = 'awskeys.json'; 5 | fp.appendFilter('JSON (*.json)', '*.json'); 6 | 7 | var result = fp.show(); 8 | 9 | switch (result) { 10 | case Components.interfaces.nsIFilePicker.returnOK: 11 | case Components.interfaces.nsIFilePicker.returnReplace: 12 | exportKeys(fp.file); 13 | break; 14 | } 15 | } 16 | 17 | function exportKeys(fout) { 18 | var data = { 19 | userAccessKeyIds: Prefs.userAccessKeyIds, 20 | userSecretAccessKeys: Prefs.userSecretAccessKeys 21 | }; 22 | 23 | var rv = FileIO.write(fout, data.toSource()); 24 | 25 | return rv; 26 | } 27 | 28 | function importJsonToKeys() { 29 | var fp = newFilePicker(); 30 | fp.init(window, 'Import and Merge keys', Components.interfaces.nsIFilePicker.modeOpen); 31 | fp.appendFilter('JSON (*.json)', '*.json'); 32 | 33 | var result = fp.show(); 34 | 35 | switch (result) { 36 | case Components.interfaces.nsIFilePicker.returnOK: 37 | case Components.interfaces.nsIFilePicker.returnReplace: 38 | importKeys(fp.file); 39 | break; 40 | } 41 | } 42 | 43 | function importKeys(fin) { 44 | var new_data = FileIO.read(fin); 45 | 46 | if (!new_data) { 47 | alert("Cannnot read file."); 48 | return false; 49 | } 50 | 51 | try { 52 | new_data = eval(new_data); 53 | } catch (e) { 54 | alert(e); 55 | return false; 56 | } 57 | 58 | var newUserAccessKeyIds = new_data.userAccessKeyIds; 59 | var newUserSecretAccessKeys = new_data.userSecretAccessKeys; 60 | 61 | Prefs.mergeUserAccessKeyIds(newUserAccessKeyIds); 62 | Prefs.mergeUserSecretAccessKeys(newUserSecretAccessKeys); 63 | 64 | return true; 65 | } 66 | -------------------------------------------------------------------------------- /Resources/chrome/content/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookpad/iam-fox/675f751976bab0db79766bd03a76218c20122c8c/Resources/chrome/content/favicon.png -------------------------------------------------------------------------------- /Resources/chrome/content/group-add-dialog.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 60 | 61 | -------------------------------------------------------------------------------- /Resources/chrome/content/group-detail-window.js: -------------------------------------------------------------------------------- 1 | function windowOnLoad() { 2 | var args = window.arguments[0]; 3 | document.title = 'Policies - ' + args.groupName; 4 | refreshGroupPolicy(); 5 | } 6 | 7 | function listboxOnSelect(event) { 8 | var args = window.arguments[0]; 9 | var iamcli = args.iamcli; 10 | var groupName = args.groupName; 11 | 12 | var item = event.currentTarget; 13 | 14 | if (!item || !item.value) { 15 | return; 16 | } 17 | 18 | var policyName = item.value; 19 | 20 | protect(function() { 21 | var xhr = inProgress(function() { 22 | var params = [['GroupName', groupName], ['PolicyName', policyName]]; 23 | return iamcli.query_or_die('GetGroupPolicy', params); 24 | }); 25 | 26 | var textbox = $('group-policy-textbox'); 27 | var policy = xhr.xml().GetGroupPolicyResult; 28 | textbox.value = decodeURIComponent(policy.PolicyDocument); 29 | disableUpdateButton(); 30 | }); 31 | } 32 | 33 | function inProgress(callback) { 34 | var loader = $('group-policy-loader'); 35 | loader.hidden = false; 36 | 37 | var retval = null; 38 | var exception = null; 39 | 40 | try { 41 | retval = callback(); 42 | } catch (e) { 43 | exception = e; 44 | } 45 | 46 | loader.hidden = true; 47 | 48 | if (exception) { 49 | throw exception; 50 | } 51 | 52 | return retval; 53 | } 54 | 55 | function addGroupPolicy() { 56 | var args = window.arguments[0]; 57 | var iamcli = args.iamcli; 58 | var groupName = args.groupName; 59 | var xhr = null; 60 | 61 | var policyName = prompt('Policy Name'); 62 | policyName = (policyName || '').trim(); 63 | 64 | if (policyName.length < 1) { 65 | return; 66 | } 67 | 68 | var listbox = $('group-policy-listbox'); 69 | 70 | for (var i = 0; i < listbox.itemCount; i++) { 71 | var item = listbox.getItemAtIndex(i); 72 | 73 | if (item.value == policyName) { 74 | alert('Duplicate policy'); 75 | return; 76 | } 77 | } 78 | 79 | protect(function() { 80 | inProgress(function() { 81 | var params = [ 82 | ['GroupName', groupName], 83 | ['PolicyName', policyName], 84 | ['PolicyDocument', POLICY_ALLOW_ALL] 85 | ]; 86 | 87 | iamcli.query_or_die('PutGroupPolicy', params); 88 | }); 89 | 90 | var textbox = $('group-policy-textbox'); 91 | var item = listbox.appendItem(policyName, policyName); 92 | listbox.selectItem(item); 93 | textbox.value = POLICY_ALLOW_ALL; 94 | disableUpdateButton(); 95 | }); 96 | } 97 | 98 | function deleteGroupPolicy() { 99 | var args = window.arguments[0]; 100 | var iamcli = args.iamcli; 101 | var groupName = args.groupName; 102 | 103 | var listbox = $('group-policy-listbox'); 104 | var item = listbox.selectedItem; 105 | 106 | if (!item || !item.value) { 107 | return; 108 | } 109 | 110 | var policyName = item.value; 111 | 112 | if (!confirm("Are you sure you want to delete '" + policyName + " ' ?")) { 113 | return; 114 | } 115 | 116 | protect(function() { 117 | inProgress(function() { 118 | var params = [['GroupName', groupName], ['PolicyName', policyName]]; 119 | iamcli.query_or_die('DeleteGroupPolicy', params); 120 | }); 121 | 122 | var textbox = $('group-policy-textbox'); 123 | listbox.removeItemAt(listbox.currentIndex); 124 | listbox.clearSelection(); 125 | textbox.value = null; 126 | }); 127 | } 128 | 129 | function refreshGroupPolicy() { 130 | var args = window.arguments[0]; 131 | var iamcli = args.iamcli; 132 | var groupName = args.groupName; 133 | 134 | protect(function() { 135 | var policyNames = []; 136 | 137 | var walk = function(marker) { 138 | var params = [['GroupName', groupName]]; 139 | 140 | if (marker) { 141 | params.push(['Marker', marker]) 142 | } 143 | 144 | var xhr = inProgress(function() { 145 | return iamcli.query_or_die('ListGroupPolicies', params); 146 | }); 147 | 148 | var xml = xhr.xml(); 149 | 150 | for each (var member in xml..PolicyNames.member) { 151 | policyNames.push(member); 152 | } 153 | 154 | var isTruncated = ((xml..IsTruncated || '').toString().trim().toLowerCase() == 'true'); 155 | 156 | return isTruncated ? (xml..Marker || '').toString().trim() : null; 157 | }.bind(this); 158 | 159 | var marker = null; 160 | 161 | do { 162 | marker = walk(marker); 163 | } while (marker); 164 | 165 | var listbox = $('group-policy-listbox'); 166 | var textbox = $('group-policy-textbox'); 167 | 168 | listbox.clearSelection(); 169 | textbox.value = null; 170 | disableUpdateButton(); 171 | 172 | for (var i = listbox.itemCount - 1; i >= 0; i--) { 173 | listbox.removeItemAt(i); 174 | } 175 | 176 | for (var i = 0; i < policyNames.length; i++) { 177 | var policyName = policyNames[i]; 178 | listbox.appendItem(policyName, policyName); 179 | } 180 | }); 181 | } 182 | 183 | function updateGroupPolicy() { 184 | var args = window.arguments[0]; 185 | var iamcli = args.iamcli; 186 | var groupName = args.groupName; 187 | 188 | var listbox = $('group-policy-listbox'); 189 | var textbox = $('group-policy-textbox'); 190 | 191 | var item = listbox.selectedItem; 192 | 193 | if (!item || !item.value) { 194 | return; 195 | } 196 | 197 | var policyName = item.value; 198 | var policyDocument = (textbox.value || '').trim(); 199 | 200 | protect(function() { 201 | inProgress(function() { 202 | var params = [ 203 | ['GroupName', groupName], 204 | ['PolicyName', policyName], 205 | ['PolicyDocument', policyDocument] 206 | ]; 207 | 208 | iamcli.query_or_die('PutGroupPolicy', params); 209 | }); 210 | 211 | textbox.value = policyDocument; 212 | disableUpdateButton(); 213 | }); 214 | } 215 | 216 | function enableUpdateButton() { 217 | $('group-policy-update-button').disabled = false; 218 | } 219 | 220 | function disableUpdateButton() { 221 | $('group-policy-update-button').disabled = true; 222 | } 223 | -------------------------------------------------------------------------------- /Resources/chrome/content/group-detail-window.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 67 | 68 | -------------------------------------------------------------------------------- /Resources/chrome/content/group-tabpanel.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Path: 35 | 36 | 37 | 38 | 39 | 40 | Search: 41 | 42 |