Skip to content

Private memory store #292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e5642cc
finished implementation for private-memory-store, added .gitignore to…
isc-rsaptars Sep 29, 2023
74a4fac
added tests, updated constructor
isc-rsaptars Oct 2, 2023
767ac5c
started work on credential manager
isc-rsaptars Oct 3, 2023
a7aefb0
done with basic implementation
isc-rsaptars Oct 3, 2023
2cf5b64
credential manager now tracking token owner in private store key
isc-rsaptars Oct 4, 2023
231d929
tracking owner by iris username instead of PID
isc-rsaptars Oct 4, 2023
0244f78
updated how username is retreived
isc-rsaptars Oct 4, 2023
83e2bb9
added some type annotations
isc-rsaptars Oct 11, 2023
115eaca
token acquired
isc-rsaptars Oct 18, 2023
acd230d
Add prerelease designation (for ongoing work in this branch)
isc-tleavitt Oct 18, 2023
45f96a9
Fix compilation error
isc-tleavitt Oct 18, 2023
4df9c82
final end of rotation commit
isc-rsaptars Oct 20, 2023
374ba22
resolved merge conflicts
isc-rsaptars Oct 20, 2023
45410ed
Merge branch 'main' into private-memory-store
isc-etamarch Jan 27, 2025
258209e
Initial changes
isc-etamarch Feb 19, 2025
e73827d
Working prototype
isc-etamarch Mar 18, 2025
d0affe6
Fix minor bug
isc-etamarch Mar 19, 2025
3c200cc
Fix redirect
isc-etamarch Mar 20, 2025
ba361b6
Fix auto-detect endpoints
isc-etamarch Mar 20, 2025
f9bd616
Fix error logging
isc-etamarch Mar 20, 2025
e66ff72
permissions for SSLConfig
isc-etamarch Mar 20, 2025
d9646e9
Readme and bug fixes
isc-etamarch Mar 21, 2025
9438c32
Fix connecting to remote
isc-etamarch Mar 21, 2025
2898bff
Add useful error
isc-etamarch Mar 21, 2025
cde36fc
Helpful additions to the OAuth page
isc-etamarch Mar 21, 2025
a55e1fa
Use GIT_ASKPASS
isc-etamarch Apr 17, 2025
0739644
Merge branch 'main' into private-memory-store
isc-etamarch Apr 17, 2025
8d832f9
update git command
isc-etamarch Apr 18, 2025
5278c85
Fix empty git url
isc-etamarch Apr 18, 2025
8b48508
testing
isc-etamarch Apr 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.vscode/
.gitattributes
*.code-workspace
*.code-workspace

# using TestClass.cls for objecscript functionality exploration / convince myself that things work the way I expect them to
cls/SourceControl/Git/TestClass.cls
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ Assuming you have the local and remote repositories created,
`git config core.sshCommand 'ssh -i ~/.ssh/<private key name>'`
8. Test the refresh button for the remote branches on the WebUI, fetch from the source control menu in Studio or VS Code, and `git fetch` in Git Bash. All 3 should work without any issues.

### HTTPS Support
We recommend that people connect to their remote git repository using SSH. If you cannot use SSH connections, we also have support for HTTPS connection through OAuth2. See [our documentation for setting up an https connection](/docs/https.md).

## Support

If you find a bug or would like to request an enhancement, [report an issue](https://github.com/intersystems/git-source-control/issues/new). If you have a question, post it on the [InterSystems Developer Community](https://community.intersystems.com/) - consider using the "Git" and "Source Control" tags as appropriate.
Expand Down
3 changes: 3 additions & 0 deletions cls/SourceControl/Git/Extension.cls
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ XData Menu
<MenuBase>
<Menu Name="%SourceMenu" Type="0">
<MenuItem Name="Status" />
<MenuItem Name="Authenticate" />
<MenuItem Name="Settings" />
<MenuItem Name="Init" />
<MenuItem Name="GitWebUI" Save="101" />
Expand Down Expand Up @@ -193,6 +194,7 @@ Method OnSourceMenuItem(name As %String, ByRef Enabled As %String, ByRef Display
set Enabled = $CASE(name,
"Status": 1,
"GitWebUI" : 1,
"Authenticate":1,
"Import": 1,
"ImportForce": 1,
"NewBranch": BranchLocked,
Expand All @@ -206,6 +208,7 @@ Method OnSourceMenuItem(name As %String, ByRef Enabled As %String, ByRef Display
// cases
"Status": 1,
"GitWebUI" : 1,
"Authenticate" : 1,
"Export": 1,
"ExportForce": 1,
"Import": 1,
Expand Down
60 changes: 60 additions & 0 deletions cls/SourceControl/Git/OAuth2.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Include %syPrompt

IncludeGenerator %syPrompt

Class SourceControl.Git.OAuth2 Extends %RegisteredObject
{

/// GenerateVerifier returns a cryptographically random 32 byte value
ClassMethod GenerateVerifier() As %String
{
new $NAMESPACE
set $NAMESPACE = "%SYS"
return ##class(%SYSTEM.Encryption).GenCryptRand(32)
}

/// Builds the authorization code URL for the given configuration
ClassMethod AuthCodeURL(c As SourceControl.Git.OAuth2.Config, namespace As %String, Output state, Output verifier) As %String
{
set state = namespace_"_"_..GenerateVerifier()
set verifier = ..GenerateVerifier()
set url = c.AuthCodeURL(state, verifier)
return url
}

ClassMethod GetURLsFromRemote(remote As %String, Output authCodeURL, Output tokenURL) As %Boolean
{
if remote [ "//github.com/" {
set authCodeURL = "https://github.com/login/oauth/authorize"
set tokenURL = "https://github.com/login/oauth/access_token"
return 1
} elseif remote [ "gitlab" {
set gitlaburl = $Piece(remote, ".com", 1) _ ".com/"
set authCodeURL = gitlaburl _ "/oauth/authorize"
set tokenUTL = gitlaburl _ "/oauth/token"
return 1
} else {
return 0
}
}

ClassMethod SetRemoteURLWithToken(remote As %String) As %String
{
set token = ##class(SourceControl.Git.Util.CredentialManager).GetToken($username, .err, .code)
if ('(remote [ token)) {
set post = $piece(remote,"https://",2)
if (post [ "@") {
set post = $piece(post, "@",2)
}
set remote = "https://oauth2:"_token_"@"_post

}
return remote
}

ClassMethod GetToken() As %String
{
return ##class(SourceControl.Git.Util.CredentialManager).GetToken($username, .err, .code)
}

}
223 changes: 223 additions & 0 deletions cls/SourceControl/Git/OAuth2/Config.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
Class SourceControl.Git.OAuth2.Config Extends %Persistent
{

/// Name is the identifier for this configuration
Property Name As %String(MAXLEN = 127);

/// ClientID is the OAuth Application ID. Stored in private memopry store only accessible by user
Property ClientID As %String(MAXLEN = "") [ Transient ];

/// ClientSecret is the OAuth Application secret. Stored in private memopry store only accessible by user
Property ClientSecret As %String(MAXLEN = "") [ Transient ];

/// Endpoint contains the resource server's token endpoint
Property Endpoint As Endpoint;

/// RedirectURL is the URL to redirect the auth token
/// to after authenticating with the resource owner
Property RedirectURL As %String(MAXLEN = "");

Property state As %String;

Property verifier As %String;

/// Scopes specifies the list of scopes we are requesting access to
Property Scopes As %List;

Property Username As %String;

Index Username On Username [ IdKey, Unique ];

Method ClientIDSet(InputValue As %String) As %Status
{
set code = "", error = ""
do ##class(SourceControl.Git.Util.CredentialManager).SetKeyPair(..Username,"clientid",InputValue, .error, .code)
if (code '= 1) || (error '= "") {
return $$$ERROR($$$GeneralError,"Set failed with following error: "_error)
}
return $$$OK
}

Method ClientIDGet() As %String
{
return ##class(SourceControl.Git.Util.CredentialManager).GetKeyPair(..Username,"clientid", .error, .code)
}

Method ClientSecretSet(InputValue As %String) As %Status
{
set code = "", error = ""
do ##class(SourceControl.Git.Util.CredentialManager).SetKeyPair(..Username,"clientsecret",InputValue, .error, .code)
if (code '= 1) || (error '= "") {
return $$$ERROR($$$GeneralError,"Set failed with following error: "_error)
}
return $$$OK
}

Method ClientSecretGet() As %String
{
return ##class(SourceControl.Git.Util.CredentialManager).GetKeyPair(..Username,"clientsecret", .error, .code)
}

ClassMethod GetConfig(username As %String) As SourceControl.Git.OAuth2.Config
{
set config = ##class(SourceControl.Git.OAuth2.Config).%OpenId(username)

return config
}

// TODO: We will need a authStyleCache when we use autodetect for Endpoint.AuthStyle in the future

Method %OnNew(configName As %String, clientID As %String, clientSecret As %String, authEndpoint As %String, tokenEndpoint As %String, redirectURL As %String, scopes As %List = "") As %Status
{
set ..Name = configName
set ..Username = $username
set ..ClientID = clientID
set ..ClientSecret = clientSecret
set ..Endpoint = ##class(Endpoint).%New()
set ..Endpoint.AuthURL = authEndpoint
set ..Endpoint.TokenURL = tokenEndpoint
set ..RedirectURL = redirectURL


if ('scopes) {
set scopes = $lb("repo")
}
set ..Scopes = scopes

return $$$OK
}

Method AuthCodeURL(state As %String, verifier As %String) As %String
{
#; new $NAMESPACE
#; set $NAMESPACE = "%SYS"

set params("response_type") = "code"
set params("client_id") = ..ClientID
set:(..RedirectURL '= "") params("redirect_uri") = ..RedirectURL
set:(state '= "") params("state") = state
set:($LISTLENGTH(..Scopes) > 0) params("scope") = $LISTTOSTRING(..Scopes," ")
if verifier {
set code = ##class(%SYSTEM.Encryption).SHAHash(256, verifier)
set params("code_challenge_method") = "S256"
set params("code_challenge") = code
}

return ..GetURLWithParams(..Endpoint.AuthURL, .params)
}

Method Exchange(authCode As %String, verifier As %String, Output sc As %Status) As %String
{
do ##class(%Net.URLParser).Decompose(..Endpoint.TokenURL, .urlComponents)

set request = ##class(%Net.HttpRequest).%New()
set request.Server = urlComponents("host")
set request.Https = (urlComponents("scheme")="https")
do request.SetParam("grant_type", "authorization_code")
do request.SetParam("code", authCode)
do request.SetParam("code_verifier", verifier)
do:(..ClientID '= "") request.SetParam("client_id", ..ClientID)
do:(..ClientSecret '= "") request.SetParam("client_secret", ..ClientSecret)
// we don't need the redirect_uri parameter because we will be consuming the token here

// TODO: also add support to put client creds in header instead of params
// either is allowed, will have to try both to see which succeeds
// this is when we will need the `AuthStyle` parameter in the endpoint
do request.SetHeader("Accept", "application/json")

do ..CreateSSLConfigIfNonExistent("GitExtensionForIris")

set request.SSLConfiguration = "GitExtensionForIris"
set sc = request.Get(urlComponents("path"))
if sc '= $$$OK {
// something went wrong
return ""
}

try {
set obj = {}.%FromJSON(request.HttpResponse.Data)
} catch ex {
set sc = ex.AsStatus()
return ""
}

if obj.%IsDefined("access_token") && (obj.%GetTypeOf("access_token") = "string") {
return obj.%Get("access_token")
} else {
set sc = $$$ERROR($$$GeneralError,"Unable to read access_token from response")
return ""
}
}

ClassMethod CreateSSLConfigIfNonExistent(name As %String)
{
do ##class(%zpkg.isc.sc.git.SSLConfig).CreateSSLConfigIfNonExistent(name)
}

ClassMethod GetURLWithParams(url As %String, ByRef params As %String) As %String
{
if $find(url, "?") {
set url = url_"&"
} else {
set url = url_"?"
}

set curParamKey = ""
for {
set isFirstIter = (curParamKey = "")
set curParamKey = $order(params(curParamKey), 1, curParamValue)

set isLastIter = (curParamKey = "")
set:'(isFirstIter || isLastIter) url = url_"&"

quit:(isLastIter)

// TODO-etamarch -> I had issues with $$$URLENCODE, need to look into this later
set url = url_$$$URLENCODE(curParamKey)_"="_$$$URLENCODE(curParamValue)
}
return url
}

Storage Default
{
<Data name="ConfigDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>ClientID</Value>
</Value>
<Value name="3">
<Value>ClientSecret</Value>
</Value>
<Value name="4">
<Value>Endpoint</Value>
</Value>
<Value name="5">
<Value>RedirectURL</Value>
</Value>
<Value name="6">
<Value>Scopes</Value>
</Value>
<Value name="7">
<Value>Username</Value>
</Value>
<Value name="8">
<Value>state</Value>
</Value>
<Value name="9">
<Value>verifier</Value>
</Value>
<Value name="10">
<Value>Name</Value>
</Value>
</Data>
<DataLocation>^SourceControl.Git.O7826.ConfigD</DataLocation>
<DefaultData>ConfigDefaultData</DefaultData>
<IdLocation>^SourceControl.Git.O7826.ConfigD</IdLocation>
<IndexLocation>^SourceControl.Git.O7826.ConfigI</IndexLocation>
<StreamLocation>^SourceControl.Git.O7826.ConfigS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}
30 changes: 30 additions & 0 deletions cls/SourceControl/Git/OAuth2/Endpoint.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Class SourceControl.Git.OAuth2.Endpoint Extends %SerialObject
{

Property AuthURL As %String;

Property DeviceAuthURL As %String;

Property TokenURL As %String;

// TODO: Might also need a Property to describe the auth style (i.e either in the header, or in the body)

Storage Default
{
<Data name="EndpointState">
<Value name="1">
<Value>AuthURL</Value>
</Value>
<Value name="2">
<Value>DeviceAuthURL</Value>
</Value>
<Value name="3">
<Value>TokenURL</Value>
</Value>
</Data>
<State>EndpointState</State>
<StreamLocation>^SourceControl.Git7826.EndpointS</StreamLocation>
<Type>%Storage.Serial</Type>
}

}
Loading
Loading