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 58 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 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
e7b0afd
allow api
isc-etamarch Apr 21, 2025
83a2c21
Tidy up work
isc-etamarch Apr 21, 2025
9500097
Merge branch 'main' into private-memory-store
isc-etamarch Apr 22, 2025
bd56c56
Fix missing brackets
isc-etamarch Apr 22, 2025
3dc68fa
Update module.xml
isc-etamarch Apr 22, 2025
aea6cfb
Cleaned up code; More user friendly
isc-etamarch Apr 23, 2025
6c6738f
add opt-in to OAuth
isc-etamarch May 2, 2025
6157d6a
Merge branch 'main' into private-memory-store
isc-etamarch May 2, 2025
2a2178b
Fix bug
isc-etamarch May 6, 2025
ee03164
Fix recognizing command bug
isc-etamarch May 6, 2025
bec6dd7
More errors
isc-etamarch May 6, 2025
03f4955
Fix OAuth
isc-etamarch May 6, 2025
c569756
Create askpass file to use for OAuth
isc-etamarch May 7, 2025
7cfec34
Debugging
isc-etamarch May 7, 2025
ee376c5
More debug
isc-etamarch May 7, 2025
1380ca2
fix issue
isc-etamarch May 7, 2025
2f8ab64
Make sure Askpass is being used
isc-etamarch May 7, 2025
93965db
Wuotes
isc-etamarch May 7, 2025
4a2706a
Fixc command
isc-etamarch May 7, 2025
0712082
Fix pathing
isc-etamarch May 7, 2025
67b8e89
Fix
isc-etamarch May 7, 2025
4098e69
Fix command
isc-etamarch May 7, 2025
03162ba
morew debugging
isc-etamarch May 7, 2025
99a0b18
Make sure OAuth is used
isc-etamarch May 7, 2025
62f0807
keep baseArgs
isc-etamarch May 7, 2025
5293d61
move to BaseArgs
isc-etamarch May 7, 2025
1de8c70
Add env variables
isc-etamarch May 7, 2025
05974d1
Remove debugging code
isc-etamarch May 8, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Partial support for production decomposition with the new interoperability editors
- Added Lock Branch setting to prevent switching branches for a protected namespace (#709)
- Tooltips on branch operations in Git UI (#725)
- Support for https connections (#279)

### Fixed
- Changing system mode (environment name) in settings persists after instance restart (#655)
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Embedded Git support for InterSystems platforms, supporting unified source contr
```
d ##class(SourceControl.Git.API).Configure()
```
This will also allow you to generate an SSH key for use as (e.g.) a deploy key and to initialize or clone a git repo.
This will also allow you to generate an SSH key for use as (e.g.) a deploy key and to initialize or clone a git repo. (If you want to use https instead, please see the documentation [here])(/docs/https.md)
3. If using VSCode: Set up `isfs` server-side editing. First, save your current workspace in which you have the code open. Then, open the `.code-workspace` file generated by VS Code and add the following to the list of folders:
```
{
Expand Down 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
59 changes: 59 additions & 0 deletions cls/SourceControl/Git/OAuth2.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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 FixRemoteURL(url As %String) As %String
{
/// OAuth Https authentication requires connecting as api
if ($extract(url,1,5) = "https") {
if (url [ "@") {
return url
} else {
set url = "https://api@"_$piece(url,"https://",2)
}
}
return url
}

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

}
219 changes: 219 additions & 0 deletions cls/SourceControl/Git/OAuth2/Config.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
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

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)

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>
}

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

Property AuthURL As %String;

Property DeviceAuthURL As %String;

Property TokenURL As %String;


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