Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 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
fbbe012
Add option to set token manually
isc-etamarch May 13, 2025
a5fa32e
FIx formatting of oauth page
isc-etamarch May 13, 2025
91b7e70
Add switch label
isc-etamarch May 13, 2025
002b5cf
Merge branch 'main' into private-memory-store
isc-etamarch May 15, 2025
58c152f
Merge branch 'main' into private-memory-store
isc-etamarch May 23, 2025
241d7b8
Fix test fail
isc-etamarch May 23, 2025
7f02d6e
Fix undefined error
isc-etamarch May 28, 2025
36a0d85
Fix bug
isc-etamarch May 28, 2025
5423212
Fix failure of git
isc-etamarch May 28, 2025
78801e0
Fix setting empty remote when remote is already empty
isc-etamarch May 28, 2025
54c5d6e
Fix
isc-etamarch May 28, 2025
02b18bf
Fix operator order
isc-etamarch May 29, 2025
b113fc8
Fix test error
isc-etamarch May 29, 2025
ee96052
Merge branch 'main' of https://github.com/intersystems/git-source-con…
isc-pbarton Jul 31, 2025
f28dc3d
Fix variety of issues
isc-dchui Dec 22, 2025
a61f8f8
Store changes for now
isc-dchui Jan 6, 2026
fc18a97
Some cleanup and better UX
isc-dchui Jan 6, 2026
50efc5d
Minor refactor
isc-dchui Jan 14, 2026
8846020
Fix a few minor things
isc-dchui Jan 15, 2026
e4c989a
Merge branch 'main' into private-memory-store
isc-dchui Jan 15, 2026
ef48baf
Fix some error handling
isc-dchui Jan 16, 2026
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 @@ -106,6 +106,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 @@ -22,7 +22,7 @@ Embedded Git support for InterSystems platforms, supporting unified source contr
```
do ##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 @@ -151,6 +151,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).

## Editing files in the Git repository server-side

There are some circumstances where you'll want to edit files in the Git repository on the IRIS server. For example,
Expand Down
10 changes: 5 additions & 5 deletions cls/SourceControl/Git/API.cls
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,18 @@ ClassMethod Configure()

/// API for git pull - just wraps Utils
/// - pTerminateOnError: if set to 1, this will terminate on error if there are any errors in the pull, otherwise will return status
ClassMethod Pull(pTerminateOnError As %Boolean = 0)
ClassMethod Pull(pTerminateOnError As %Boolean = 0) As %Status
{
set st = ##class(SourceControl.Git.Utils).Pull(,pTerminateOnError)
if pTerminateOnError && $$$ISERR(st) {
set sc = ##class(SourceControl.Git.Utils).Pull(,pTerminateOnError)
if pTerminateOnError && $$$ISERR(sc) {
Do $System.Process.Terminate($Job,1)
}
quit st
quit sc
}

/// Imports all items from the Git repository into IRIS.
/// - pForce: if true, will import an item even if the last updated timestamp in IRIS is later than that of the file on disk.
ClassMethod ImportAll(pForce As %Boolean = 0) as %Status
ClassMethod ImportAll(pForce As %Boolean = 0) As %Status
{
return ##class(SourceControl.Git.Utils).ImportAll(pForce)
}
Expand Down
2 changes: 1 addition & 1 deletion cls/SourceControl/Git/Build.cls
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ ClassMethod BuildUIForDevMode(devMode As %Boolean, rootDirectory As %String)
write !, $zf(-100, "/SHELL", "npm", "run", "build", "--prefix", webUIDirectory)
}

}
}
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
1 change: 0 additions & 1 deletion cls/SourceControl/Git/Log.cls
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,3 @@ Storage Default
}

}

151 changes: 151 additions & 0 deletions cls/SourceControl/Git/OAuth2.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
Include %syPrompt

IncludeGenerator %syPrompt

Class SourceControl.Git.OAuth2 Extends %RegisteredObject
{

/// GenerateVerifier returns a cryptographically random string compliant with RFC 7636 (PKCE)
/// Requirements:
/// - Minimum 43 characters, maximum 128 characters
/// - Character set: [A-Za-z0-9-._~] (unreserved URI characters only)
/// - High entropy (256+ bits)
///
/// Implementation: Generates 64 random characters (384 bits entropy) from the allowed charset
ClassMethod GenerateVerifier() As %String
{
// RFC 7636 unreserved characters: [A-Za-z0-9-._~]
set charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
set charsetLen = $LENGTH(charset)

// Generate 64 character verifier (384 bits entropy, well above 256-bit minimum)
set verifier = ""
new $NAMESPACE
set $NAMESPACE = "%SYS"

for i=1:1:64 {
// Get random byte (0-255)
set randomByte = $ASCII(##class(%SYSTEM.Encryption).GenCryptRand(1))
// Map to charset index (1-66)
set index = (randomByte # charsetLen) + 1
set verifier = verifier _ $EXTRACT(charset, index)
}

return verifier
}

/// 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 = ..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 tokenURL = 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).GetAccessToken($username, .err, .code)
}

/// Base64URLEncode converts data to base64url format (RFC 4648 Section 5)
/// Base64url is like standard base64 but URL-safe:
/// - Replace + with -
/// - Replace / with _
/// - Remove padding (=)
/// Used for PKCE code_challenge encoding per RFC 7636
ClassMethod Base64URLEncode(data As %String) As %String
{
// Convert to standard base64
set base64 = $SYSTEM.Encryption.Base64Encode(data)

// Convert to base64url: replace +/= with -_
set base64url = $REPLACE(base64, "+", "-")
set base64url = $REPLACE(base64url, "/", "_")

// Remove padding
set base64url = $REPLACE(base64url, "=", "")

return base64url
}

ClassMethod DeleteOAuthConfig(username As %String) As %Status
{
set sc = $$$OK
set error = ""
set code = ""

// Delete access token
// Check if token exists first
set token = ##class(SourceControl.Git.Util.CredentialManager).GetAccessToken(username, .error, .code)
if error '= "" {
return $$$ERROR($$$GeneralError,$$$FormatText("Failed to get access token with error: %1; code: %2'",error,code))
}
if token '= "" {
do ##class(SourceControl.Git.Util.CredentialManager).DeleteAccessToken(username, .error, .code)
if error '= "" {
return $$$ERROR($$$GeneralError,$$$FormatText("Failed to delete access token with error: %1; code: %2'",error,code))
}
}

// Clear refresh token
do ##class(SourceControl.Git.Util.CredentialManager).SetRefreshToken(username, "", .error, .code)
if error '= "" {
return $$$ERROR($$$GeneralError,$$$FormatText("Failed to clear refresh token with error: %1; code: %2'",error,code))
}

// Clear client ID; secret
do ##class(SourceControl.Git.Util.CredentialManager).SetKeyPair(username, "clientid", "", .error, .code)
if error '= "" {
return $$$ERROR($$$GeneralError,$$$FormatText("Failed to clear clientid with error: %1; code: %2'",error,code))
}
do ##class(SourceControl.Git.Util.CredentialManager).SetKeyPair(username, "clientsecret", "", .error, .code)
if error '= "" {
return $$$ERROR($$$GeneralError,$$$FormatText("Failed to clear clientsecret with error: %1; code: %2'",error,code))
}
do ##class(SourceControl.Git.Util.CredentialManager).SetKeyPair(username, "state", "", .error, .code)
if error '= "" {
return $$$ERROR($$$GeneralError,$$$FormatText("Failed to clear state with error: %1; code: %2'",error,code))
}
do ##class(SourceControl.Git.Util.CredentialManager).SetKeyPair(username, "verifier", "", .error, .code)
if error '= "" {
return $$$ERROR($$$GeneralError,$$$FormatText("Failed to clear verifier with error: %1; code: %2'",error,code))
}

// Delete persistent config object
if ##class(SourceControl.Git.OAuth2.Config).%ExistsId(username) {
set sc = ##class(SourceControl.Git.OAuth2.Config).%DeleteId(username)
}
return sc
}

}
Loading
Loading