From 8f4bab9c3fab575c8dc8215ac8acd248aabdbc38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Dias?= Date: Thu, 5 Nov 2020 21:29:24 -0300 Subject: [PATCH] Implement GitHub Personal Access Tokens Fixes #1392. https://developer.github.com/v3/#authentication --- .../IceTokenCredentials.extension.st | 23 +++ .../IceTipAddTokenCredentialCommand.class.st | 42 +++++ .../IceTipAddTokenCredentialsModel.class.st | 163 ++++++++++++++++++ .../IceTipEditTokenCredentialsModel.class.st | 65 +++++++ .../IceTokenCredentials.extension.st | 11 ++ Iceberg/IceTokenCredentials.class.st | 83 +++++++++ 6 files changed, 387 insertions(+) create mode 100644 Iceberg-Plugin-GitHub/IceTokenCredentials.extension.st create mode 100644 Iceberg-TipUI/IceTipAddTokenCredentialCommand.class.st create mode 100644 Iceberg-TipUI/IceTipAddTokenCredentialsModel.class.st create mode 100644 Iceberg-TipUI/IceTipEditTokenCredentialsModel.class.st create mode 100644 Iceberg-TipUI/IceTokenCredentials.extension.st create mode 100644 Iceberg/IceTokenCredentials.class.st diff --git a/Iceberg-Plugin-GitHub/IceTokenCredentials.extension.st b/Iceberg-Plugin-GitHub/IceTokenCredentials.extension.st new file mode 100644 index 0000000000..65215bb52e --- /dev/null +++ b/Iceberg-Plugin-GitHub/IceTokenCredentials.extension.st @@ -0,0 +1,23 @@ +Extension { #name : #IceTokenCredentials } + +{ #category : #'*Iceberg-Plugin-GitHub' } +IceTokenCredentials >> applyToRequest: aRequest [ + + token ifNil: [ ^ self ]. + + aRequest + headerAt: self authorizationHeaderKey + put: self authorizationHeaderValue +] + +{ #category : #'*Iceberg-Plugin-GitHub' } +IceTokenCredentials >> authorizationHeaderKey [ + + ^ 'Authorization' +] + +{ #category : #'*Iceberg-Plugin-GitHub' } +IceTokenCredentials >> authorizationHeaderValue [ + + ^ 'Bearer ' , token +] diff --git a/Iceberg-TipUI/IceTipAddTokenCredentialCommand.class.st b/Iceberg-TipUI/IceTipAddTokenCredentialCommand.class.st new file mode 100644 index 0000000000..820046c7e8 --- /dev/null +++ b/Iceberg-TipUI/IceTipAddTokenCredentialCommand.class.st @@ -0,0 +1,42 @@ +Class { + #name : #IceTipAddTokenCredentialCommand, + #superclass : #IceTipAbstractCredentialStoreCommand, + #category : #'Iceberg-TipUI-Credentials' +} + +{ #category : #activation } +IceTipAddTokenCredentialCommand class >> browserShortcutActivation [ + + + ^ IceTipToolbarActivation + byRootGroupItemFor: IceTipCredentialsStoreContext + order: 100 +] + +{ #category : #accessing } +IceTipAddTokenCredentialCommand class >> defaultHelp [ + ^ 'Add a new token credential' +] + +{ #category : #accessing } +IceTipAddTokenCredentialCommand class >> defaultMenuIconName [ + ^ #add +] + +{ #category : #accessing } +IceTipAddTokenCredentialCommand class >> defaultMenuItemName [ + ^ 'Token' +] + +{ #category : #accessing } +IceTipAddTokenCredentialCommand class >> defaultPosition [ + ^ #right +] + +{ #category : #execution } +IceTipAddTokenCredentialCommand >> execute [ + IceTipAddTokenCredentialsModel new + credentialStore: store; + tool: tool; + openNonModal. +] diff --git a/Iceberg-TipUI/IceTipAddTokenCredentialsModel.class.st b/Iceberg-TipUI/IceTipAddTokenCredentialsModel.class.st new file mode 100644 index 0000000000..2d2ccd672f --- /dev/null +++ b/Iceberg-TipUI/IceTipAddTokenCredentialsModel.class.st @@ -0,0 +1,163 @@ +" +I am a dialog to add token credentials. +" +Class { + #name : #IceTipAddTokenCredentialsModel, + #superclass : #IceTipAbstractAskCredentialsModel, + #instVars : [ + 'hostInput', + 'tokenInput', + 'tokenLabel', + 'hostLabel', + 'usernameInput', + 'usernameLabel', + 'tool' + ], + #category : #'Iceberg-TipUI-Credentials' +} + +{ #category : #specs } +IceTipAddTokenCredentialsModel class >> defaultSpec [ + + + ^ SpecLayout composed newColumn: [ :col | + col + newRow: [:row | row add: #usernameLabel width: 80 * self currentWorld displayScaleFactor; add: #usernameInput ] height: self inputTextHeight; + newRow: [:row | row add: #hostLabel width: 80 * self currentWorld displayScaleFactor; add: #hostInput ] height: self inputTextHeight; + newRow: [:row | row add: #tokenLabel width: 80 * self currentWorld displayScaleFactor; add: #tokenInput ] height: self inputTextHeight; + newRow: [:row |] + ] + +] + +{ #category : #actions } +IceTipAddTokenCredentialsModel >> cancelAction [ + "Do nothing" +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> credentials [ + + ^ IceTokenCredentials new + username: self usernameFromInput; + token: self tokenFromInput; + host: self hostnameFromInput; + yourself +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> hostInput [ + ^ hostInput +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> hostLabel [ + ^ hostLabel +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> hostnameFromInput [ + + ^ hostInput text +] + +{ #category : #testing } +IceTipAddTokenCredentialsModel >> initialExtent [ + + ^ (350 @ (self class inputTextHeight * 5 + self class buttonHeight)) scaledByDisplayScaleFactor +] + +{ #category : #initialization } +IceTipAddTokenCredentialsModel >> initializeWidgets [ + + super initializeWidgets. + usernameLabel := self newLabel label: 'Username: '. + usernameInput := self newTextInput + autoAccept: true; + whenTextChanged: [ self updateOkButton ]; + yourself. + hostLabel := self newLabel label: 'Host: '. + hostInput := self newTextInput + ghostText: 'e.g. github.com'; + autoAccept: true; + whenTextChanged: [ self updateOkButton ]; + yourself. + tokenLabel := self newLabel label: 'Token: '. + tokenInput := self newTextInput + beEncrypted; + autoAccept: true; + whenTextChanged: [ self updateOkButton ]; + yourself. + +] + +{ #category : #testing } +IceTipAddTokenCredentialsModel >> isOkEnabled [ + + ^ self usernameFromInput isNotEmpty and: [ + self hostnameFromInput isNotEmpty and: [ + self tokenFromInput isNotEmpty ] ] +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> okAction [ + + credentialStore + storeCredential: self credentials + forHostname: self hostnameFromInput. + + accepted := true. + tool refresh +] + +{ #category : #initialization } +IceTipAddTokenCredentialsModel >> putFocusOrder [ + + self focusOrder + add: usernameInput; + add: hostInput; + add: tokenInput +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> title [ + + ^ 'Add token credentials' +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> tokenFromInput [ + + ^ tokenInput text +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> tokenInput [ + ^ tokenInput +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> tokenLabel [ + ^ tokenLabel +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> tool: anIceTipCredentialsStoreBrowser [ + tool := anIceTipCredentialsStoreBrowser +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> usernameFromInput [ + + ^ usernameInput text +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> usernameInput [ + ^ usernameInput +] + +{ #category : #accessing } +IceTipAddTokenCredentialsModel >> usernameLabel [ + ^ usernameLabel +] diff --git a/Iceberg-TipUI/IceTipEditTokenCredentialsModel.class.st b/Iceberg-TipUI/IceTipEditTokenCredentialsModel.class.st new file mode 100644 index 0000000000..7a475ba926 --- /dev/null +++ b/Iceberg-TipUI/IceTipEditTokenCredentialsModel.class.st @@ -0,0 +1,65 @@ +" +I am a dialog to edit token credentials. +" +Class { + #name : #IceTipEditTokenCredentialsModel, + #superclass : #IceTipAddTokenCredentialsModel, + #instVars : [ + 'credentials' + ], + #category : #'Iceberg-TipUI-Credentials' +} + +{ #category : #accessing } +IceTipEditTokenCredentialsModel >> credentials [ + + ^ credentials +] + +{ #category : #accessing } +IceTipEditTokenCredentialsModel >> credentials: anIceCredentials [ + + credentials := anIceCredentials. + self refreshPresenterFromCredentials +] + +{ #category : #initialization } +IceTipEditTokenCredentialsModel >> initializeWidgets [ + super initializeWidgets. + + hostInput disable. +] + +{ #category : #accessing } +IceTipEditTokenCredentialsModel >> okAction [ + + self refreshCredentialsFromPresenter. + credentialStore storeCredential: self credentials. + tool refresh. + +] + +{ #category : #accessing } +IceTipEditTokenCredentialsModel >> refreshCredentialsFromPresenter [ + + credentials + username: self usernameFromInput; + token: self tokenFromInput; + host: self hostnameFromInput + +] + +{ #category : #accessing } +IceTipEditTokenCredentialsModel >> refreshPresenterFromCredentials [ + + usernameInput text: credentials username. + tokenInput text: credentials token. + hostInput text: credentials host. + +] + +{ #category : #TOREMOVE } +IceTipEditTokenCredentialsModel >> title [ + + ^ 'Edit token credentials' +] diff --git a/Iceberg-TipUI/IceTokenCredentials.extension.st b/Iceberg-TipUI/IceTokenCredentials.extension.st new file mode 100644 index 0000000000..5abd6da41c --- /dev/null +++ b/Iceberg-TipUI/IceTokenCredentials.extension.st @@ -0,0 +1,11 @@ +Extension { #name : #IceTokenCredentials } + +{ #category : #'*Iceberg-TipUI' } +IceTokenCredentials >> askForModelClass [ + ^ self notYetImplemented +] + +{ #category : #'*Iceberg-TipUI' } +IceTokenCredentials >> editModelClass [ + ^ IceTipEditTokenCredentialsModel +] diff --git a/Iceberg/IceTokenCredentials.class.st b/Iceberg/IceTokenCredentials.class.st new file mode 100644 index 0000000000..1caf3dbb89 --- /dev/null +++ b/Iceberg/IceTokenCredentials.class.st @@ -0,0 +1,83 @@ +" +I implement authentication credentials via OAUTH-like token. + +## Examples + +* https://developer.github.com/v3/#authentication +* https://confluence.atlassian.com/bitbucketserver0514/using-bitbucket-server/personal-access-tokens + +" +Class { + #name : #IceTokenCredentials, + #superclass : #IceAbstractCredentials, + #instVars : [ + 'token', + 'username' + ], + #category : #'Iceberg-Security' +} + +{ #category : #printing } +IceTokenCredentials >> description [ + ^ 'User: ', username +] + +{ #category : #printing } +IceTokenCredentials >> hostDescription [ + ^ host +] + +{ #category : #accessing } +IceTokenCredentials >> isPresent [ + ^ self token notEmpty +] + +{ #category : #accessing } +IceTokenCredentials >> password [ + "For git operations in GitHub, the token can be used as password. + + See https://docs.github.com/en/enterprise/2.15/user/articles/creating-a-personal-access-token-for-the-command-line#using-a-token-on-the-command-line" + + ^ token +] + +{ #category : #removing } +IceTokenCredentials >> removeFrom: anIceCredentialStore [ + + anIceCredentialStore removePlainTextCredential: self +] + +{ #category : #'API - storing' } +IceTokenCredentials >> storeInto: aCredentialStore forHostname: aHost [ + + host := aHost. + aCredentialStore storePlaintextCredential: self forHostname: aHost +] + +{ #category : #accessing } +IceTokenCredentials >> token [ + + ^ token +] + +{ #category : #accessing } +IceTokenCredentials >> token: aString [ + + token := aString +] + +{ #category : #accessing } +IceTokenCredentials >> type [ + + ^ 'Token' +] + +{ #category : #accessing } +IceTokenCredentials >> username [ + ^ username +] + +{ #category : #accessing } +IceTokenCredentials >> username: aString [ + username := aString +]