Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2d645f9
feat: add access token management APIs
legnoh Oct 30, 2025
e0c8835
refactor: wrap mock response in TokenInfos structure for GetTokens test
legnoh Oct 30, 2025
0d568be
fix: update token structure to include Issuer field in GetTokens and …
legnoh Oct 30, 2025
bcf85b0
fix: update token tests to use Subject and Issuer fields instead of U…
legnoh Oct 30, 2025
78cc2ce
refactor: remove unused createRefreshableAccessTokenParams function
legnoh Oct 30, 2025
f4f5af1
fix: update OrderBy field in GetTokensParams document
legnoh Dec 12, 2025
6f189fb
refactor: remove LastUsed field from GetTokensParams and related logic
legnoh Dec 16, 2025
726af38
fix: update response status check in RevokeTokenByID to handle NoContent
legnoh Dec 17, 2025
4220434
Add retry to POST build scan trigger if needed (#1252)
attiasas Oct 30, 2025
079241b
Xray Remediation Endpoints (#1233)
attiasas Nov 3, 2025
bf54a5c
JGC-427 - Skip broken repositories tests (#1256)
ehl-jf Nov 3, 2025
1eb2f9c
Removed repo field in AQL query (#1258)
naveenku-jfrog Nov 5, 2025
73f9f67
support for direct-download cmd (#1199)
reshmifrog Nov 6, 2025
09b78c6
JGC-429 - Bump go 1.25 (#1259)
ehl-jf Nov 10, 2025
9ccf6d8
Fix get details API (#1260)
dortam888 Nov 10, 2025
d3570d1
update build-info-go dependency (#1262)
reshmifrog Nov 14, 2025
8194171
changing the build-info-go version
nitinp19 Nov 18, 2025
b347e35
Fix an issue that evidence encoded url twice resulting in a wrong url…
dortam888 Nov 19, 2025
c687c13
Xray Get Violations API (#1186)
attiasas Nov 19, 2025
f03db7b
JGC-000 - Remove auto label (#1266)
RemiBou Nov 19, 2025
c2ab78e
Fix application details resolution from api (#1268)
dortam888 Dec 2, 2025
e25eff5
On-demand log level to debug and set props recursively for props (#1269)
fluxxBot Dec 9, 2025
9742f99
Git Integration POST Request (#1272)
orto17 Dec 9, 2025
67d0ca2
Fix Remediation after API breaking change (#1275)
attiasas Dec 11, 2025
9798380
Fix: Handle Empty/Uninitialized .git Directory Gracefully (#1277)
agrasth Dec 12, 2025
23fc9f0
Fix: Temp Directory Cleanup Continues on Permission Errors (#1276)
agrasth Dec 12, 2025
8f9334e
malicious-code-scanner (#1080)
barv-jfrog Dec 16, 2025
1b73690
Align config profile structure to recent changes in API body (#1274)
eranturgeman Dec 16, 2025
cbf1b0d
fixed TestXscSendGitIntegrationEvent (#1281)
orto17 Dec 17, 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
34 changes: 0 additions & 34 deletions .github/workflows/tests-on-pr.yml

This file was deleted.

36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@
- [Creating an Access Token](#creating-an-access-token)
- [Refreshing an Access Token](#refreshing-an-access-token)
- [Exchanging an OIDC Access Token](#exchanging-an-oidc-access-token)
- [Getting Access Tokens](#getting-access-tokens)
- [Getting an Access Token by ID](#getting-an-access-token-by-id)
- [Revoking an Access Token by ID](#revoking-an-access-token-by-id)
- [Distribution APIs](#distribution-apis)
- [Creating Distribution Service Manager](#creating-distribution-service-manager)
- [Creating Distribution Details](#creating-distribution-details)
Expand Down Expand Up @@ -1879,6 +1882,37 @@ params := services.CreateOidcTokenParams{
response, err = servicesManager.ExchangeOidcToken(params)
```

#### Getting Access Tokens

```go
params := services.GetTokensParams{
// Optional filters
Description: "my-token-description", // Filter by token description
Username: "admin", // Filter by username
Refreshable: utils.Pointer(true), // Filter by refreshable status
TokenId: "token-id", // Filter by specific token ID
OrderBy: "token_id", // Order by field (created|token_id|owner|subject|expiry)
DescendingOrder: utils.Pointer(false), // Sort order (true for descending)
}

tokens, err := accessManager.GetTokens(params)
```

#### Getting an Access Token by ID

```go
token, err := accessManager.GetTokenByID("my-token-id")

# currently used token
token, err := accessManager.GetTokenByID("me")
```

#### Revoking an Access Token by ID

```go
err := accessManager.RevokeTokenByID("my-token-id")
```

## Distribution APIs

### Creating Distribution Service Manager
Expand Down Expand Up @@ -3548,6 +3582,8 @@ fmt.Printf("Application Name: %s\n", application.ApplicationName)
fmt.Printf("Application Key: %s\n", application.ApplicationKey)
fmt.Printf("Project Name: %s\n", application.ProjectName)
fmt.Printf("Project Key: %s\n", application.ProjectKey)
fmt.Printf("Criticality: %s\n", application.Criticality)
fmt.Printf("Maturity Level: %s\n", application.MaturityLevel)
```

### Get Application Version Promotions
Expand Down
18 changes: 18 additions & 0 deletions access/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,21 @@ func (sm *AccessServicesManager) ExchangeOidcToken(params services.CreateOidcTok
tokenService.ServiceDetails = sm.config.GetServiceDetails()
return tokenService.ExchangeOidcToken(params)
}

func (sm *AccessServicesManager) GetTokens(params services.GetTokensParams) ([]services.TokenInfo, error) {
tokenService := services.NewTokenService(sm.client)
tokenService.ServiceDetails = sm.config.GetServiceDetails()
return tokenService.GetTokens(params)
}

func (sm *AccessServicesManager) GetTokenByID(tokenId string) (*services.TokenInfo, error) {
tokenService := services.NewTokenService(sm.client)
tokenService.ServiceDetails = sm.config.GetServiceDetails()
return tokenService.GetTokenByID(tokenId)
}

func (sm *AccessServicesManager) RevokeTokenByID(tokenId string) error {
tokenService := services.NewTokenService(sm.client)
tokenService.ServiceDetails = sm.config.GetServiceDetails()
return tokenService.RevokeTokenByID(tokenId)
}
108 changes: 107 additions & 1 deletion access/services/accesstoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package services
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"

"github.com/jfrog/jfrog-client-go/auth"
"github.com/jfrog/jfrog-client-go/http/jfroghttpclient"
clientutils "github.com/jfrog/jfrog-client-go/utils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/io/httputils"
"net/http"
)

// #nosec G101 -- False positive - no hardcoded credentials.
Expand Down Expand Up @@ -50,6 +53,31 @@ type CreateOidcTokenParams struct {
ApplicationKey string `json:"application_key,omitempty"`
}

type GetTokensParams struct {
Description string `url:"description,omitempty"`
Username string `url:"username,omitempty"`
Refreshable *bool `url:"refreshable,omitempty"`
TokenId string `url:"token_id,omitempty"`
OrderBy string `url:"order_by,omitempty"`
DescendingOrder *bool `url:"descending_order,omitempty"`
}

type TokenInfos struct {
Tokens []TokenInfo `json:"tokens"`
}

type TokenInfo struct {
TokenId string `json:"token_id"`
Subject string `json:"subject"`
Expiry int64 `json:"expiry,omitempty"`
IssuedAt int64 `json:"issued_at"`
Issuer string `json:"issuer"`
Description string `json:"description,omitempty"`
Refreshable bool `json:"refreshable,omitempty"`
Scope string `json:"scope,omitempty"`
LastUsed int64 `json:"last_used,omitempty"`
}

func NewCreateTokenParams(params CreateTokenParams) CreateTokenParams {
return CreateTokenParams{CommonTokenParams: params.CommonTokenParams, IncludeReferenceToken: params.IncludeReferenceToken}
}
Expand Down Expand Up @@ -127,6 +155,84 @@ func (ps *TokenService) ExchangeOidcToken(params CreateOidcTokenParams) (auth.Oi
return tokenInfo, errorutils.CheckError(err)
}

func (ps *TokenService) GetTokens(params GetTokensParams) ([]TokenInfo, error) {
httpDetails := ps.ServiceDetails.CreateHttpClientDetails()
requestUrl := fmt.Sprintf("%s%s", ps.ServiceDetails.GetUrl(), tokensApi)

// Build query parameters manually
queryParams := url.Values{}
if params.Description != "" {
queryParams.Add("description", params.Description)
}
if params.Username != "" {
queryParams.Add("username", params.Username)
}
if params.Refreshable != nil {
queryParams.Add("refreshable", strconv.FormatBool(*params.Refreshable))
}
if params.TokenId != "" {
queryParams.Add("token_id", params.TokenId)
}
if params.OrderBy != "" {
queryParams.Add("order_by", params.OrderBy)
}
if params.DescendingOrder != nil {
queryParams.Add("descending_order", strconv.FormatBool(*params.DescendingOrder))
}

if queryString := queryParams.Encode(); queryString != "" {
requestUrl += "?" + queryString
}

resp, body, _, err := ps.client.SendGet(requestUrl, true, &httpDetails)
if err != nil {
return nil, err
}
if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil {
return nil, err
}

var tokenInfos TokenInfos
err = json.Unmarshal(body, &tokenInfos)
return tokenInfos.Tokens, errorutils.CheckError(err)
}

func (ps *TokenService) GetTokenByID(tokenId string) (*TokenInfo, error) {
if tokenId == "" {
return nil, errorutils.CheckErrorf("token ID cannot be empty")
}

httpDetails := ps.ServiceDetails.CreateHttpClientDetails()
url := fmt.Sprintf("%s%s/%s", ps.ServiceDetails.GetUrl(), tokensApi, tokenId)

resp, body, _, err := ps.client.SendGet(url, true, &httpDetails)
if err != nil {
return nil, err
}
if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil {
return nil, err
}

var tokenInfo TokenInfo
err = json.Unmarshal(body, &tokenInfo)
return &tokenInfo, errorutils.CheckError(err)
}

func (ps *TokenService) RevokeTokenByID(tokenId string) error {
if tokenId == "" {
return errorutils.CheckErrorf("token ID cannot be empty")
}

httpDetails := ps.ServiceDetails.CreateHttpClientDetails()
url := fmt.Sprintf("%s%s/%s", ps.ServiceDetails.GetUrl(), tokensApi, tokenId)

resp, body, err := ps.client.SendDelete(url, nil, &httpDetails)
if err != nil {
return err
}
return errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusNoContent)
}

func prepareForRefresh(p CreateTokenParams) (*CreateTokenParams, error) {
// Validate provided parameters
if p.RefreshToken == "" {
Expand Down
14 changes: 6 additions & 8 deletions apptrust/services/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

const (
applicationDetailsAPI = "apptrust/api/v1/applications"
applicationDetailsAPI = "api/v1/applications"
)

type ApplicationService struct {
Expand Down Expand Up @@ -49,12 +49,12 @@ func (as *ApplicationService) GetApplicationDetails(applicationKey string) (*App
return nil, err
}

var applicationResponse ApplicationResponse
if err = json.Unmarshal(body, &applicationResponse); err != nil {
var application Application
if err = json.Unmarshal(body, &application); err != nil {
return nil, errorutils.CheckError(err)
}

return &applicationResponse.Application, nil
return &application, nil
}

func (as *ApplicationService) GetApplicationVersionPromotions(applicationKey, applicationVersion string, queryParams map[string]string) (*ApplicationPromotionsResponse, error) {
Expand Down Expand Up @@ -85,15 +85,13 @@ func (as *ApplicationService) GetApplicationVersionPromotions(applicationKey, ap
return &promotionsResponse, nil
}

type ApplicationResponse struct {
Application Application `json:"application"`
}

type Application struct {
ApplicationName string `json:"application_name"`
ApplicationKey string `json:"application_key"`
ProjectName string `json:"project_name"`
ProjectKey string `json:"project_key"`
Criticality string `json:"criticality"`
MaturityLevel string `json:"maturity_level"`
}

type ApplicationPromotionsResponse struct {
Expand Down
36 changes: 19 additions & 17 deletions apptrust/services/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import (

const mockApplicationKey = "test-app-key"

var mockApplicationResponse = ApplicationResponse{
Application: Application{
ApplicationName: "Test Application",
ApplicationKey: "test-app-key",
ProjectName: "Test Project",
ProjectKey: "test-proj",
},
var mockApplication = Application{
ApplicationName: "Test Application",
ApplicationKey: "test-app-key",
ProjectName: "Test Project",
ProjectKey: "test-proj",
Criticality: "high",
MaturityLevel: "production",
}

func TestApplicationService_GetApplicationDetails_Success(t *testing.T) {
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusOK, mockApplicationResponse)
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusOK, mockApplication)

mockServer, applicationService := createMockApplicationServer(t, handlerFunc)
defer mockServer.Close()
Expand All @@ -35,11 +35,13 @@ func TestApplicationService_GetApplicationDetails_Success(t *testing.T) {
assert.Equal(t, "test-app-key", application.ApplicationKey)
assert.Equal(t, "Test Project", application.ProjectName)
assert.Equal(t, "test-proj", application.ProjectKey)
assert.Equal(t, "high", application.Criticality)
assert.Equal(t, "production", application.MaturityLevel)
assert.Equal(t, 1, *requestNum)
}

func TestApplicationService_GetApplicationDetails_NotFound(t *testing.T) {
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusNotFound, ApplicationResponse{})
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusNotFound, Application{})

mockServer, applicationService := createMockApplicationServer(t, handlerFunc)
defer mockServer.Close()
Expand All @@ -51,7 +53,7 @@ func TestApplicationService_GetApplicationDetails_NotFound(t *testing.T) {
}

func TestApplicationService_GetApplicationDetails_BadRequest(t *testing.T) {
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusBadRequest, ApplicationResponse{})
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusBadRequest, Application{})

mockServer, applicationService := createMockApplicationServer(t, handlerFunc)
defer mockServer.Close()
Expand All @@ -63,7 +65,7 @@ func TestApplicationService_GetApplicationDetails_BadRequest(t *testing.T) {
}

func TestApplicationService_GetApplicationDetails_Unauthorized(t *testing.T) {
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusUnauthorized, ApplicationResponse{})
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusUnauthorized, Application{})

mockServer, applicationService := createMockApplicationServer(t, handlerFunc)
defer mockServer.Close()
Expand All @@ -75,7 +77,7 @@ func TestApplicationService_GetApplicationDetails_Unauthorized(t *testing.T) {
}

func TestApplicationService_GetApplicationDetails_Forbidden(t *testing.T) {
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusForbidden, ApplicationResponse{})
handlerFunc, requestNum := createApplicationHandlerFunc(t, http.StatusForbidden, Application{})

mockServer, applicationService := createMockApplicationServer(t, handlerFunc)
defer mockServer.Close()
Expand All @@ -89,7 +91,7 @@ func TestApplicationService_GetApplicationDetails_Forbidden(t *testing.T) {
func TestApplicationService_GetApplicationDetails_InvalidJSON(t *testing.T) {
requestNum := 0
handlerFunc := func(w http.ResponseWriter, r *http.Request) {
expectedURI := "/apptrust/api/v1/applications/" + mockApplicationKey
expectedURI := "/api/v1/applications/" + mockApplicationKey
if r.RequestURI == expectedURI {
assert.Equal(t, "GET", r.Method)
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
Expand Down Expand Up @@ -150,11 +152,11 @@ func createMockApplicationServer(t *testing.T, testHandler http.HandlerFunc) (*h
return testServer, NewApplicationService(apptrustDetails, client)
}

func createApplicationHandlerFunc(t *testing.T, statusCode int, response ApplicationResponse) (http.HandlerFunc, *int) {
func createApplicationHandlerFunc(t *testing.T, statusCode int, response Application) (http.HandlerFunc, *int) {
requestNum := 0
return func(w http.ResponseWriter, r *http.Request) {
expectedURI := "/apptrust/api/v1/applications/" + mockApplicationKey
if r.RequestURI == expectedURI || r.RequestURI == "/apptrust/api/v1/applications/non-existent-key" || r.RequestURI == "/apptrust/api/v1/applications/invalid-key" {
expectedURI := "/api/v1/applications/" + mockApplicationKey
if r.RequestURI == expectedURI || r.RequestURI == "/api/v1/applications/non-existent-key" || r.RequestURI == "/api/v1/applications/invalid-key" {
assert.Equal(t, "GET", r.Method)
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))

Expand All @@ -168,7 +170,7 @@ func createApplicationHandlerFunc(t *testing.T, statusCode int, response Applica
}, &requestNum
}

func writeMockApplicationResponse(t *testing.T, w http.ResponseWriter, response ApplicationResponse) {
func writeMockApplicationResponse(t *testing.T, w http.ResponseWriter, response Application) {
content, err := json.Marshal(response)
assert.NoError(t, err)
_, err = w.Write(content)
Expand Down
Loading