mirror of https://github.com/jumpserver/jumpserver
feat: PAM Service (#14552)
* feat: PAM Service * perf: import package name --------- Co-authored-by: jiangweidong <1053570670@qq.com>pull/14556/head
parent
70912cdb95
commit
e05930109b
|
@ -2,3 +2,4 @@ from .account import *
|
||||||
from .task import *
|
from .task import *
|
||||||
from .template import *
|
from .template import *
|
||||||
from .virtual import *
|
from .virtual import *
|
||||||
|
from .service import *
|
||||||
|
|
|
@ -12,6 +12,7 @@ from assets.models import Asset, Node
|
||||||
from authentication.permissions import UserConfirmation, ConfirmType
|
from authentication.permissions import UserConfirmation, ConfirmType
|
||||||
from common.api.mixin import ExtraFilterFieldsMixin
|
from common.api.mixin import ExtraFilterFieldsMixin
|
||||||
from common.permissions import IsValidUser
|
from common.permissions import IsValidUser
|
||||||
|
from common.drf.filters import AttrRulesFilterBackend
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ __all__ = [
|
||||||
class AccountViewSet(OrgBulkModelViewSet):
|
class AccountViewSet(OrgBulkModelViewSet):
|
||||||
model = Account
|
model = Account
|
||||||
search_fields = ('username', 'name', 'asset__name', 'asset__address', 'comment')
|
search_fields = ('username', 'name', 'asset__name', 'asset__address', 'comment')
|
||||||
|
extra_filter_backends = [AttrRulesFilterBackend]
|
||||||
filterset_class = AccountFilterSet
|
filterset_class = AccountFilterSet
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.AccountSerializer,
|
'default': serializers.AccountSerializer,
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils import translation
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from accounts import serializers
|
||||||
|
from accounts.models import ServiceIntegration
|
||||||
|
from audits.models import ServiceAccessLog
|
||||||
|
from authentication.permissions import UserConfirmation, ConfirmType
|
||||||
|
from common.exceptions import JMSException
|
||||||
|
from common.permissions import IsValidUser
|
||||||
|
from common.utils import get_request_ip
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceIntegrationViewSet(OrgBulkModelViewSet):
|
||||||
|
model = ServiceIntegration
|
||||||
|
search_fields = ('name', 'comment')
|
||||||
|
serializer_classes = {
|
||||||
|
'default': serializers.ServiceIntegrationSerializer,
|
||||||
|
'get_account_secret': serializers.ServiceAccountSecretSerializer
|
||||||
|
}
|
||||||
|
rbac_perms = {
|
||||||
|
'get_once_secret': 'accounts.change_serviceintegration',
|
||||||
|
'get_account_secret': 'accounts.view_serviceintegration',
|
||||||
|
}
|
||||||
|
|
||||||
|
@action(
|
||||||
|
['GET'], detail=False, url_path='sdks',
|
||||||
|
permission_classes=[IsValidUser]
|
||||||
|
)
|
||||||
|
def get_sdks_info(self, request, *args, **kwargs):
|
||||||
|
readme = ''
|
||||||
|
sdk_language = self.request.query_params.get('language', 'python')
|
||||||
|
filename = f'readme.{translation.get_language()}.md'
|
||||||
|
readme_path = os.path.join(
|
||||||
|
settings.APPS_DIR, 'accounts', 'demos', sdk_language, filename
|
||||||
|
)
|
||||||
|
if os.path.exists(readme_path):
|
||||||
|
with open(readme_path, 'r') as f:
|
||||||
|
readme = f.read()
|
||||||
|
return Response(data={'readme': readme })
|
||||||
|
|
||||||
|
@action(
|
||||||
|
['GET'], detail=True, url_path='secret',
|
||||||
|
permission_classes=[RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||||
|
)
|
||||||
|
def get_once_secret(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
secret = instance.get_secret()
|
||||||
|
return Response(data={'id': instance.id, 'secret': secret})
|
||||||
|
|
||||||
|
@action(['GET'], detail=False, url_path='account-secret')
|
||||||
|
def get_account_secret(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.query_params)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response({'error': serializer.errors}, status=400)
|
||||||
|
|
||||||
|
service = request.user
|
||||||
|
account = service.get_account(**serializer.data)
|
||||||
|
if not account:
|
||||||
|
msg = _('Account not found')
|
||||||
|
raise JMSException(code='Not found', detail='%s' % msg)
|
||||||
|
asset = account.asset
|
||||||
|
ServiceAccessLog.objects.create(
|
||||||
|
remote_addr=get_request_ip(request), service=service.name, service_id=service.id,
|
||||||
|
account=f'{account.name}({account.username})', asset=f'{asset.name}({asset.address})',
|
||||||
|
)
|
||||||
|
return Response(data={'id': request.user.id, 'secret': account.secret})
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
# JumpServer PAM Client
|
||||||
|
|
||||||
|
This package provides a Go client for interacting with the JumpServer PAM API to retrieve secrets for various assets. It simplifies the process of sending requests and handling responses.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Validate parameters before sending requests.
|
||||||
|
- Support for both asset and account-based secret retrieval.
|
||||||
|
- Easy integration with JumpServer PAM API using HMAC-SHA256 signatures for authentication.
|
||||||
|
|
||||||
|
## Usage Instructions
|
||||||
|
|
||||||
|
1. **Download Go Code Files**:
|
||||||
|
Download the code files into your project directory.
|
||||||
|
|
||||||
|
2. **Import the Package**:
|
||||||
|
Import the package in your Go file, and you can directly use its functionalities.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `Go 1.16+`
|
||||||
|
- `github.com/google/uuid`
|
||||||
|
- `gopkg.in/twindagger/httpsig.v1`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Initialization
|
||||||
|
|
||||||
|
To use the JumpServer PAM client, create an instance by providing the required `endpoint`, `keyID`, and `keySecret`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"your_module_path/jms_pam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := jms_pam.NewJumpServerPAM(
|
||||||
|
"http://127.0.0.1", // Replace with your JumpServer endpoint
|
||||||
|
"your-key-id", // Replace with your actual Key ID
|
||||||
|
"your-key-secret", // Replace with your actual Key Secret
|
||||||
|
"", // Leave empty for default organization ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a Secret Request
|
||||||
|
|
||||||
|
You can create a request for a secret by specifying the asset or account information.
|
||||||
|
|
||||||
|
```go
|
||||||
|
request, err := jms_pam.NewSecretRequest("Linux", "", "root", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating request:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending the Request
|
||||||
|
|
||||||
|
Send the request using the `Send` method of the client.
|
||||||
|
|
||||||
|
```go
|
||||||
|
secretObj, err := client.Send(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error sending request:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling the Response
|
||||||
|
|
||||||
|
Check if the secret was retrieved successfully and handle the response accordingly.
|
||||||
|
|
||||||
|
```go
|
||||||
|
if secretObj.Valid {
|
||||||
|
fmt.Println("Secret:", secretObj.Secret)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Get secret failed:", string(secretObj.Desc))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complete Example
|
||||||
|
|
||||||
|
Here’s a complete example of how to use the client:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"your_module_path/jms_pam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := jms_pam.NewJumpServerPAM(
|
||||||
|
"http://127.0.0.1",
|
||||||
|
"your-key-id",
|
||||||
|
"your-key-secret",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
request, err := jms_pam.NewSecretRequest("Linux", "", "root", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating request:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secretObj, err := client.Send(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error sending request:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretObj.Valid {
|
||||||
|
fmt.Println("Secret:", secretObj.Secret)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Get secret failed:", string(secretObj.Desc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The library returns errors for invalid parameters when creating a `SecretRequest`. This includes checks for valid UUIDs and ensuring that required parameters are provided.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please open an issue or submit a pull request for any enhancements or bug fixes.
|
|
@ -0,0 +1,133 @@
|
||||||
|
# JumpServer PAM クライアント
|
||||||
|
|
||||||
|
このパッケージは、JumpServer PAM API と対話し、さまざまな資産のパスワードを取得するための Go クライアントを提供します。リクエストの送信とレスポンスの処理を簡素化します。
|
||||||
|
|
||||||
|
## 機能
|
||||||
|
|
||||||
|
- リクエスト送信前にパラメータを検証します。
|
||||||
|
- 資産およびアカウントに基づくパスワード取得をサポートします。
|
||||||
|
- HMAC-SHA256 署名を使用して認証を行い、JumpServer PAM API との統合を容易にします。
|
||||||
|
|
||||||
|
## 使用手順
|
||||||
|
|
||||||
|
1. **Go コードファイルのダウンロード**:
|
||||||
|
コードファイルをプロジェクトディレクトリにダウンロードします。
|
||||||
|
|
||||||
|
2. **パッケージのインポート**:
|
||||||
|
Go ファイルにパッケージをインポートすると、その機能を直接使用できます。
|
||||||
|
|
||||||
|
## 要件
|
||||||
|
|
||||||
|
- `Go 1.16+`
|
||||||
|
- `github.com/google/uuid`
|
||||||
|
- `gopkg.in/twindagger/httpsig.v1`
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 初期化
|
||||||
|
|
||||||
|
JumpServer PAM クライアントを使用するには、必要な `endpoint`、`keyID`、および `keySecret` を提供してインスタンスを作成します。
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"your_module_path/jms_pam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := jms_pam.NewJumpServerPAM(
|
||||||
|
"http://127.0.0.1", // あなたの JumpServer エンドポイントに置き換えてください
|
||||||
|
"your-key-id", // 実際の Key ID に置き換えてください
|
||||||
|
"your-key-secret", // 実際の Key Secret に置き換えてください
|
||||||
|
"", // デフォルトの組織 ID を使用するには空のままにします
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### パスワードリクエストの作成
|
||||||
|
|
||||||
|
資産またはアカウント情報を指定してリクエストを作成できます。
|
||||||
|
|
||||||
|
```go
|
||||||
|
request, err := jms_pam.NewSecretRequest("Linux", "", "root", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("リクエスト作成中にエラー:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### リクエストの送信
|
||||||
|
|
||||||
|
クライアントの `Send` メソッドを使用してリクエストを送信します。
|
||||||
|
|
||||||
|
```go
|
||||||
|
secretObj, err := client.Send(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("リクエスト送信中にエラー:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### レスポンスの処理
|
||||||
|
|
||||||
|
パスワードが正常に取得されたかどうかを確認し、それに応じてレスポンスを処理します。
|
||||||
|
|
||||||
|
```go
|
||||||
|
if secretObj.Valid {
|
||||||
|
fmt.Println("パスワード:", secretObj.Secret)
|
||||||
|
} else {
|
||||||
|
fmt.Println("パスワードの取得に失敗:", string(secretObj.Desc))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完全な例
|
||||||
|
|
||||||
|
以下は、クライアントの使用方法に関する完全な例です。
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"your_module_path/jms_pam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := jms_pam.NewJumpServerPAM(
|
||||||
|
"http://127.0.0.1",
|
||||||
|
"your-key-id",
|
||||||
|
"your-key-secret",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
request, err := jms_pam.NewSecretRequest("Linux", "", "root", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("リクエスト作成中にエラー:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secretObj, err := client.Send(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("リクエスト送信中にエラー:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretObj.Valid {
|
||||||
|
fmt.Println("パスワード:", secretObj.Secret)
|
||||||
|
} else {
|
||||||
|
fmt.Println("パスワードの取得に失敗:", string(secretObj.Desc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## エラーハンドリング
|
||||||
|
|
||||||
|
このライブラリは、`SecretRequest` を作成する際に無効なパラメータに対するエラーを返します。これには、有効な UUID の確認や、必要なパラメータが提供されていることの確認が含まれます。
|
||||||
|
|
||||||
|
## 貢献
|
||||||
|
|
||||||
|
貢献を歓迎します!改善やバグ修正のために問題を提起したり、プルリクエストを送信してください。
|
|
@ -0,0 +1,133 @@
|
||||||
|
# JumpServer PAM 客户端
|
||||||
|
|
||||||
|
该包提供了一个 Go 客户端,用于与 JumpServer PAM API 交互,以检索各种资产的密码。它简化了发送请求和处理响应的过程。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 在发送请求之前验证参数。
|
||||||
|
- 支持基于资产和账户的密码检索。
|
||||||
|
- 使用 HMAC-SHA256 签名进行身份验证,方便与 JumpServer PAM API 集成。
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
1. **下载 Go 代码文件**:
|
||||||
|
将代码文件下载到您的项目目录中。
|
||||||
|
|
||||||
|
2. **导入包**:
|
||||||
|
在您的 Go 文件中导入该包,您即可直接使用其功能。
|
||||||
|
|
||||||
|
## 需求
|
||||||
|
|
||||||
|
- `Go 1.16+`
|
||||||
|
- `github.com/google/uuid`
|
||||||
|
- `gopkg.in/twindagger/httpsig.v1`
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 初始化
|
||||||
|
|
||||||
|
要使用 JumpServer PAM 客户端,通过提供所需的 `endpoint`、`keyID` 和 `keySecret` 创建一个实例。
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"your_module_path/jms_pam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := jms_pam.NewJumpServerPAM(
|
||||||
|
"http://127.0.0.1", // 替换为您的 JumpServer 端点
|
||||||
|
"your-key-id", // 替换为您的实际 Key ID
|
||||||
|
"your-key-secret", // 替换为您的实际 Key Secret
|
||||||
|
"", // 留空以使用默认的组织 ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建密码请求
|
||||||
|
|
||||||
|
您可以通过指定资产或账户信息来创建请求。
|
||||||
|
|
||||||
|
```go
|
||||||
|
request, err := jms_pam.NewSecretRequest("Linux", "", "root", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("创建请求时出错:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发送请求
|
||||||
|
|
||||||
|
使用客户端的 `Send` 方法发送请求。
|
||||||
|
|
||||||
|
```go
|
||||||
|
secretObj, err := client.Send(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("发送请求时出错:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 处理响应
|
||||||
|
|
||||||
|
检查密码是否成功检索,并相应地处理响应。
|
||||||
|
|
||||||
|
```go
|
||||||
|
if secretObj.Valid {
|
||||||
|
fmt.Println("密码:", secretObj.Secret)
|
||||||
|
} else {
|
||||||
|
fmt.Println("获取密码失败:", string(secretObj.Desc))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整示例
|
||||||
|
|
||||||
|
以下是如何使用该客户端的完整示例:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"your_module_path/jms_pam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := jms_pam.NewJumpServerPAM(
|
||||||
|
"http://127.0.0.1",
|
||||||
|
"your-key-id",
|
||||||
|
"your-key-secret",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
request, err := jms_pam.NewSecretRequest("Linux", "", "root", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("创建请求时出错:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secretObj, err := client.Send(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("发送请求时出错:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretObj.Valid {
|
||||||
|
fmt.Println("密码:", secretObj.Secret)
|
||||||
|
} else {
|
||||||
|
fmt.Println("获取密码失败:", string(secretObj.Desc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
该库会在创建 `SecretRequest` 时返回无效参数的错误。这包括对有效 UUID 的检查以及确保提供了必需的参数。
|
||||||
|
|
||||||
|
## 贡献
|
||||||
|
|
||||||
|
欢迎贡献!如有任何增强或错误修复,请提出问题或提交拉取请求。
|
|
@ -0,0 +1,133 @@
|
||||||
|
# JumpServer PAM 客戶端
|
||||||
|
|
||||||
|
該包提供了一個 Go 客戶端,用於與 JumpServer PAM API 交互,以檢索各種資產的密碼。它簡化了發送請求和處理響應的過程。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 在發送請求之前驗證參數。
|
||||||
|
- 支持基於資產和帳戶的密碼檢索。
|
||||||
|
- 使用 HMAC-SHA256 簽名進行身份驗證,方便與 JumpServer PAM API 集成。
|
||||||
|
|
||||||
|
## 使用說明
|
||||||
|
|
||||||
|
1. **下載 Go 代碼文件**:
|
||||||
|
將代碼文件下載到您的項目目錄中。
|
||||||
|
|
||||||
|
2. **導入包**:
|
||||||
|
在您的 Go 文件中導入該包,您即可直接使用其功能。
|
||||||
|
|
||||||
|
## 需求
|
||||||
|
|
||||||
|
- `Go 1.16+`
|
||||||
|
- `github.com/google/uuid`
|
||||||
|
- `gopkg.in/twindagger/httpsig.v1`
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 初始化
|
||||||
|
|
||||||
|
要使用 JumpServer PAM 客戶端,通過提供所需的 `endpoint`、`keyID` 和 `keySecret` 創建一個實例。
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"your_module_path/jms_pam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := jms_pam.NewJumpServerPAM(
|
||||||
|
"http://127.0.0.1", // 替換為您的 JumpServer 端點
|
||||||
|
"your-key-id", // 替換為您的實際 Key ID
|
||||||
|
"your-key-secret", // 替換為您的實際 Key Secret
|
||||||
|
"", // 留空以使用默認的組織 ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 創建密碼請求
|
||||||
|
|
||||||
|
您可以通過指定資產或帳戶信息來創建請求。
|
||||||
|
|
||||||
|
```go
|
||||||
|
request, err := jms_pam.NewSecretRequest("Linux", "", "root", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("創建請求時出錯:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 發送請求
|
||||||
|
|
||||||
|
使用客戶端的 `Send` 方法發送請求。
|
||||||
|
|
||||||
|
```go
|
||||||
|
secretObj, err := client.Send(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("發送請求時出錯:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 處理響應
|
||||||
|
|
||||||
|
檢查密碼是否成功檢索,並相應地處理響應。
|
||||||
|
|
||||||
|
```go
|
||||||
|
if secretObj.Valid {
|
||||||
|
fmt.Println("密碼:", secretObj.Secret)
|
||||||
|
} else {
|
||||||
|
fmt.Println("獲取密碼失敗:", string(secretObj.Desc))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整示例
|
||||||
|
|
||||||
|
以下是如何使用該客戶端的完整示例:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"your_module_path/jms_pam"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := jms_pam.NewJumpServerPAM(
|
||||||
|
"http://127.0.0.1",
|
||||||
|
"your-key-id",
|
||||||
|
"your-key-secret",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
request, err := jms_pam.NewSecretRequest("Linux", "", "root", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("創建請求時出錯:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secretObj, err := client.Send(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("發送請求時出錯:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretObj.Valid {
|
||||||
|
fmt.Println("密碼:", secretObj.Secret)
|
||||||
|
} else {
|
||||||
|
fmt.Println("獲取密碼失敗:", string(secretObj.Desc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 錯誤處理
|
||||||
|
|
||||||
|
該庫會在創建 `SecretRequest` 時返回無效參數的錯誤。這包括對有效 UUID 的檢查以及確保提供了必需的參數。
|
||||||
|
|
||||||
|
## 貢獻
|
||||||
|
|
||||||
|
歡迎貢獻!如有任何增強或錯誤修復,請提出問題或提交拉取請求。
|
|
@ -0,0 +1,162 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gopkg.in/twindagger/httpsig.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultOrgId = "00000000-0000-0000-0000-000000000002"
|
||||||
|
|
||||||
|
type RequestParamsError struct {
|
||||||
|
Params []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RequestParamsError) Error() string {
|
||||||
|
return fmt.Sprintf("At least one of the following fields must be provided: %v.", e.Params)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretRequest struct {
|
||||||
|
AccountID string
|
||||||
|
AssetID string
|
||||||
|
Asset string
|
||||||
|
Account string
|
||||||
|
Method string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSecretRequest(asset, assetID, account, accountID string) (*SecretRequest, error) {
|
||||||
|
req := &SecretRequest{
|
||||||
|
Asset: asset,
|
||||||
|
AssetID: assetID,
|
||||||
|
Account: account,
|
||||||
|
AccountID: accountID,
|
||||||
|
Method: http.MethodGet,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, req.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SecretRequest) validate() error {
|
||||||
|
if r.AccountID != "" {
|
||||||
|
if _, err := uuid.Parse(r.AccountID); err != nil {
|
||||||
|
return fmt.Errorf("invalid UUID: %s. Value must be a valid UUID", r.AccountID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.AssetID == "" && r.Asset == "" {
|
||||||
|
return &RequestParamsError{Params: []string{"asset", "asset_id"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Account == "" {
|
||||||
|
return &RequestParamsError{Params: []string{"account", "account_id"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.AssetID != "" {
|
||||||
|
if _, err := uuid.Parse(r.AssetID); err != nil {
|
||||||
|
return fmt.Errorf("invalid UUID: %s. Value must be a valid UUID", r.AssetID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SecretRequest) GetURL() string {
|
||||||
|
return "/api/v1/accounts/service-integrations/account-secret/"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SecretRequest) GetQuery() url.Values {
|
||||||
|
query := url.Values{}
|
||||||
|
if r.AccountID != "" {
|
||||||
|
query.Add("account_id", r.AccountID)
|
||||||
|
}
|
||||||
|
if r.AssetID != "" {
|
||||||
|
query.Add("asset_id", r.AssetID)
|
||||||
|
}
|
||||||
|
if r.Asset != "" {
|
||||||
|
query.Add("asset", r.Asset)
|
||||||
|
}
|
||||||
|
if r.Account != "" {
|
||||||
|
query.Add("account", r.Account)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
type Secret struct {
|
||||||
|
Secret string `json:"secret,omitempty"`
|
||||||
|
Desc json.RawMessage `json:"desc,omitempty"`
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromResponse(response *http.Response) Secret {
|
||||||
|
var secret Secret
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
var raw json.RawMessage
|
||||||
|
if err := json.NewDecoder(response.Body).Decode(&raw); err == nil {
|
||||||
|
secret.Desc = raw
|
||||||
|
} else {
|
||||||
|
secret.Desc = json.RawMessage(`{"error": "Unknown error occurred"}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = json.NewDecoder(response.Body).Decode(&secret)
|
||||||
|
secret.Valid = true
|
||||||
|
}
|
||||||
|
return secret
|
||||||
|
}
|
||||||
|
|
||||||
|
type JumpServerPAM struct {
|
||||||
|
Endpoint string
|
||||||
|
KeyID string
|
||||||
|
KeySecret string
|
||||||
|
OrgID string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJumpServerPAM(endpoint, keyID, keySecret, orgID string) *JumpServerPAM {
|
||||||
|
if orgID == "" {
|
||||||
|
orgID = DefaultOrgId
|
||||||
|
}
|
||||||
|
return &JumpServerPAM{
|
||||||
|
Endpoint: endpoint,
|
||||||
|
KeyID: keyID,
|
||||||
|
KeySecret: keySecret,
|
||||||
|
OrgID: orgID,
|
||||||
|
httpClient: &http.Client{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *JumpServerPAM) SignRequest(r *http.Request) error {
|
||||||
|
headers := []string{"(request-target)", "date"}
|
||||||
|
signer, err := httpsig.NewRequestSigner(c.KeyID, c.KeySecret, "hmac-sha256")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return signer.SignRequest(r, headers, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *JumpServerPAM) Send(req *SecretRequest) (Secret, error) {
|
||||||
|
fullUrl := c.Endpoint + req.GetURL()
|
||||||
|
query := req.GetQuery()
|
||||||
|
fullURL := fmt.Sprintf("%s?%s", fullUrl, query.Encode())
|
||||||
|
|
||||||
|
request, err := http.NewRequest(req.Method, fullURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return Secret{}, err
|
||||||
|
}
|
||||||
|
request.Header.Set("Accept", "application/json")
|
||||||
|
request.Header.Set("X-Source", "jms-pam")
|
||||||
|
err = c.SignRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return Secret{Desc: json.RawMessage(`{"error": "` + err.Error() + `"}`)}, nil
|
||||||
|
}
|
||||||
|
response, err := c.httpClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return Secret{Desc: json.RawMessage(`{"error": "` + err.Error() + `"}`)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return FromResponse(response), nil
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
# JumpServer PAM Client
|
||||||
|
|
||||||
|
This package provides a Python client for interacting with the JumpServer PAM API to retrieve secrets for various assets. It simplifies the process of sending requests and handling responses.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Validate parameters before sending requests.
|
||||||
|
- Support for both asset and account-based secret retrieval.
|
||||||
|
- Easy integration with JumpServer PAM API using HTTP signatures for authentication.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can install the package via pip:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install jms_pam-0.0.1-py3-none-any.whl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `Python 3.6+`
|
||||||
|
- `requests`
|
||||||
|
- `httpsig`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Initialization
|
||||||
|
|
||||||
|
To use the JumpServer PAM client, create an instance by providing the required `endpoint`, `key_id`, and `key_secret`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from jms_pam import JumpServerPAM, SecretRequest
|
||||||
|
|
||||||
|
client = JumpServerPAM(
|
||||||
|
endpoint='http://127.0.0.1',
|
||||||
|
key_id='your-key-id',
|
||||||
|
key_secret='your-key-secret'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a Secret Request
|
||||||
|
|
||||||
|
You can create a request for a secret by specifying the asset or account information.
|
||||||
|
|
||||||
|
```python
|
||||||
|
request = SecretRequest(asset='Linux', account='root')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending the Request
|
||||||
|
|
||||||
|
Send the request using the `send` method of the client.
|
||||||
|
|
||||||
|
```python
|
||||||
|
secret_obj = client.send(request)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling the Response
|
||||||
|
|
||||||
|
Check if the secret was retrieved successfully and handle the response accordingly.
|
||||||
|
|
||||||
|
```python
|
||||||
|
if secret_obj.valid:
|
||||||
|
print('Secret: %s' % secret_obj.secret)
|
||||||
|
else:
|
||||||
|
print('Get secret failed: %s' % secret_obj.desc)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complete Example
|
||||||
|
|
||||||
|
Here’s a complete example of how to use the client:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from jumpserver_pam_client import JumpServerPAM, SecretRequest
|
||||||
|
|
||||||
|
client = JumpServerPAM(
|
||||||
|
endpoint='http://127.0.0.1',
|
||||||
|
key_id='your-key-id',
|
||||||
|
key_secret='your-key-secret'
|
||||||
|
)
|
||||||
|
|
||||||
|
request = SecretRequest(asset='Linux', account='root')
|
||||||
|
secret_obj = client.send(request)
|
||||||
|
|
||||||
|
if secret_obj.valid:
|
||||||
|
print('Secret: %s' % secret_obj.secret)
|
||||||
|
else:
|
||||||
|
print('Get secret failed: %s' % secret_obj.desc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The library raises `RequestParamsError` if the parameters provided do not meet the validation requirements. This includes checks for valid UUIDs and interdependencies between parameters.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please open an issue or submit a pull request for any enhancements or bug fixes.
|
|
@ -0,0 +1,96 @@
|
||||||
|
# JumpServer PAM クライアント
|
||||||
|
|
||||||
|
このパッケージは、JumpServer PAM API と対話し、さまざまなアセットのシークレットを取得するための Python クライアントを提供します。リクエストを送信し、レスポンスを処理するプロセスを簡素化します。
|
||||||
|
|
||||||
|
## 特徴
|
||||||
|
|
||||||
|
- リクエストを送信する前にパラメータを検証します。
|
||||||
|
- アセットおよびアカウントベースのシークレット取得をサポートします。
|
||||||
|
- HTTP 署名を使用して JumpServer PAM API と簡単に統合できます。
|
||||||
|
|
||||||
|
## インストール
|
||||||
|
|
||||||
|
以下のコマンドを使用して、パッケージを pip でインストールできます:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install jms_pam-0.0.1-py3-none-any.whl
|
||||||
|
```
|
||||||
|
|
||||||
|
## 要件
|
||||||
|
|
||||||
|
- `Python 3.6+`
|
||||||
|
- `requests`
|
||||||
|
- `httpsig`
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 初期化
|
||||||
|
|
||||||
|
JumpServer PAM クライアントを使用するには、必要な `endpoint`、`key_id`、および `key_secret` を提供してインスタンスを作成します。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from jms_pam import JumpServerPAM, SecretRequest
|
||||||
|
|
||||||
|
client = JumpServerPAM(
|
||||||
|
endpoint='http://127.0.0.1',
|
||||||
|
key_id='your-key-id',
|
||||||
|
key_secret='your-key-secret'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### シークレットリクエストの作成
|
||||||
|
|
||||||
|
アセットまたはアカウント情報を指定して、シークレットのリクエストを作成できます。
|
||||||
|
|
||||||
|
```python
|
||||||
|
request = SecretRequest(asset='Linux', account='root')
|
||||||
|
```
|
||||||
|
|
||||||
|
### リクエストの送信
|
||||||
|
|
||||||
|
クライアントの `send` メソッドを使用してリクエストを送信します。
|
||||||
|
|
||||||
|
```python
|
||||||
|
secret_obj = client.send(request)
|
||||||
|
```
|
||||||
|
|
||||||
|
### レスポンスの処理
|
||||||
|
|
||||||
|
シークレットが正常に取得されたかどうかを確認し、レスポンスを適切に処理します。
|
||||||
|
|
||||||
|
```python
|
||||||
|
if secret_obj.valid:
|
||||||
|
print('秘密: %s' % secret_obj.secret)
|
||||||
|
else:
|
||||||
|
print('シークレットの取得に失敗しました: %s' % secret_obj.desc)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完全な例
|
||||||
|
|
||||||
|
以下は、クライアントの使用方法の完全な例です:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from jumpserver_pam_client import JumpServerPAM, SecretRequest
|
||||||
|
|
||||||
|
client = JumpServerPAM(
|
||||||
|
endpoint='http://127.0.0.1',
|
||||||
|
key_id='your-key-id',
|
||||||
|
key_secret='your-key-secret'
|
||||||
|
)
|
||||||
|
|
||||||
|
request = SecretRequest(asset='Linux', account='root')
|
||||||
|
secret_obj = client.send(request)
|
||||||
|
|
||||||
|
if secret_obj.valid:
|
||||||
|
print('秘密: %s' % secret_obj.secret)
|
||||||
|
else:
|
||||||
|
print('シークレットの取得に失敗しました: %s' % secret_obj.desc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## エラーハンドリング
|
||||||
|
|
||||||
|
ライブラリは、提供されたパラメータが検証要件を満たしていない場合に `RequestParamsError` を発生させます。これには、有効な UUID の確認やパラメータ間の相互依存性のチェックが含まれます。
|
||||||
|
|
||||||
|
## 貢献
|
||||||
|
|
||||||
|
貢献を歓迎します!改善やバグ修正のために、問題を開くかプルリクエストを送信してください。
|
|
@ -0,0 +1,96 @@
|
||||||
|
# JumpServer PAM 客户端
|
||||||
|
|
||||||
|
该包提供了一个 Python 客户端,用于与 JumpServer PAM API 交互,以检索各种资产的密码。它简化了发送请求和处理响应的过程。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- 在发送请求之前验证参数。
|
||||||
|
- 支持基于资产和账户的密码检索。
|
||||||
|
- 通过 HTTP 签名轻松集成 JumpServer PAM API。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
您可以通过 pip 安装该包:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install jms_pam-0.0.1-py3-none-any.whl
|
||||||
|
```
|
||||||
|
|
||||||
|
## 需求
|
||||||
|
|
||||||
|
- `Python 3.6+`
|
||||||
|
- `requests`
|
||||||
|
- `httpsig`
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 初始化
|
||||||
|
|
||||||
|
要使用 JumpServer PAM 客户端,通过提供所需的 `endpoint`、`key_id` 和 `key_secret` 创建一个实例。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from jms_pam import JumpServerPAM, SecretRequest
|
||||||
|
|
||||||
|
client = JumpServerPAM(
|
||||||
|
endpoint='http://127.0.0.1',
|
||||||
|
key_id='your-key-id',
|
||||||
|
key_secret='your-key-secret'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建密码请求
|
||||||
|
|
||||||
|
您可以通过指定资产或账户信息来创建一个密码请求。
|
||||||
|
|
||||||
|
```python
|
||||||
|
request = SecretRequest(asset='Linux', account='root')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发送请求
|
||||||
|
|
||||||
|
使用客户端的 `send` 方法发送请求。
|
||||||
|
|
||||||
|
```python
|
||||||
|
secret_obj = client.send(request)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 处理响应
|
||||||
|
|
||||||
|
检查密码是否成功检索,并相应地处理响应。
|
||||||
|
|
||||||
|
```python
|
||||||
|
if secret_obj.valid:
|
||||||
|
print('密码: %s' % secret_obj.secret)
|
||||||
|
else:
|
||||||
|
print('获取密码失败: %s' % secret_obj.desc)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整示例
|
||||||
|
|
||||||
|
以下是如何使用该客户端的完整示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from jms_pam import JumpServerPAM, SecretRequest
|
||||||
|
|
||||||
|
client = JumpServerPAM(
|
||||||
|
endpoint='http://127.0.0.1',
|
||||||
|
key_id='your-key-id',
|
||||||
|
key_secret='your-key-secret'
|
||||||
|
)
|
||||||
|
|
||||||
|
request = SecretRequest(asset='Linux', account='root')
|
||||||
|
secret_obj = client.send(request)
|
||||||
|
|
||||||
|
if secret_obj.valid:
|
||||||
|
print('密码: %s' % secret_obj.secret)
|
||||||
|
else:
|
||||||
|
print('获取密码失败: %s' % secret_obj.desc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
如果提供的参数不符合验证要求,库会引发 `RequestParamsError`。这包括对有效 UUID 的检查和参数之间的相互依赖性检查。
|
||||||
|
|
||||||
|
## 贡献
|
||||||
|
|
||||||
|
欢迎贡献!请打开一个问题或提交拉取请求,以进行任何增强或修复错误。
|
|
@ -0,0 +1,96 @@
|
||||||
|
# JumpServer PAM 客戶端
|
||||||
|
|
||||||
|
此套件提供了一個 Python 客戶端,用於與 JumpServer PAM API 互動,以檢索各種資產的秘密。它簡化了發送請求和處理響應的過程。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- 在發送請求之前驗證參數。
|
||||||
|
- 支持基於資產和帳戶的秘密檢索。
|
||||||
|
- 通過 HTTP 簽名輕鬆集成 JumpServer PAM API。
|
||||||
|
|
||||||
|
## 安裝
|
||||||
|
|
||||||
|
您可以通過 pip 安裝此套件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install jms_pam-0.0.1-py3-none-any.whl
|
||||||
|
```
|
||||||
|
|
||||||
|
## 需求
|
||||||
|
|
||||||
|
- `Python 3.6+`
|
||||||
|
- `requests`
|
||||||
|
- `httpsig`
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 初始化
|
||||||
|
|
||||||
|
要使用 JumpServer PAM 客戶端,通過提供所需的 `endpoint`、`key_id` 和 `key_secret` 創建一個實例。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from jms_pam import JumpServerPAM, SecretRequest
|
||||||
|
|
||||||
|
client = JumpServerPAM(
|
||||||
|
endpoint='http://127.0.0.1',
|
||||||
|
key_id='your-key-id',
|
||||||
|
key_secret='your-key-secret'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 創建秘密請求
|
||||||
|
|
||||||
|
您可以通過指定資產或帳戶信息來創建一個秘密請求。
|
||||||
|
|
||||||
|
```python
|
||||||
|
request = SecretRequest(asset='Linux', account='root')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 發送請求
|
||||||
|
|
||||||
|
使用客戶端的 `send` 方法發送請求。
|
||||||
|
|
||||||
|
```python
|
||||||
|
secret_obj = client.send(request)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 處理響應
|
||||||
|
|
||||||
|
檢查秘密是否成功檢索,並相應地處理響應。
|
||||||
|
|
||||||
|
```python
|
||||||
|
if secret_obj.valid:
|
||||||
|
print('秘密: %s' % secret_obj.secret)
|
||||||
|
else:
|
||||||
|
print('獲取秘密失敗: %s' % secret_obj.desc)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整示例
|
||||||
|
|
||||||
|
以下是如何使用該客戶端的完整示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from jumpserver_pam_client import JumpServerPAM, SecretRequest
|
||||||
|
|
||||||
|
client = JumpServerPAM(
|
||||||
|
endpoint='http://127.0.0.1',
|
||||||
|
key_id='your-key-id',
|
||||||
|
key_secret='your-key-secret'
|
||||||
|
)
|
||||||
|
|
||||||
|
request = SecretRequest(asset='Linux', account='root')
|
||||||
|
secret_obj = client.send(request)
|
||||||
|
|
||||||
|
if secret_obj.valid:
|
||||||
|
print('秘密: %s' % secret_obj.secret)
|
||||||
|
else:
|
||||||
|
print('獲取秘密失敗: %s' % secret_obj.desc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 錯誤處理
|
||||||
|
|
||||||
|
如果提供的參數不符合驗證要求,該庫將引發 `RequestParamsError`。這包括對有效 UUID 的檢查和參數之間的相互依賴性檢查。
|
||||||
|
|
||||||
|
## 貢獻
|
||||||
|
|
||||||
|
歡迎貢獻!請打開一個問題或提交拉取請求,以進行任何增強或修復錯誤。
|
|
@ -0,0 +1 @@
|
||||||
|
from .main import JumpServerPAM, SecretRequest
|
|
@ -0,0 +1,145 @@
|
||||||
|
import requests
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
from httpsig.requests_auth import HTTPSignatureAuth
|
||||||
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_ORG_ID = '00000000-0000-0000-0000-000000000002'
|
||||||
|
|
||||||
|
|
||||||
|
class RequestParamsError(ValueError):
|
||||||
|
def __init__(self, params):
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
msg = "At least one of the following fields must be provided: %s."
|
||||||
|
return 'RequestParamsError: (%s)' % msg % ', '.join(self.params)
|
||||||
|
|
||||||
|
|
||||||
|
class SecretRequest(object):
|
||||||
|
"""
|
||||||
|
Validate parameters and their interdependencies.
|
||||||
|
Parameters:
|
||||||
|
account_id (str): The account ID, must be a valid UUID.
|
||||||
|
asset_id (str): The asset ID, must be a valid UUID.
|
||||||
|
asset (str): The name of the asset, can be empty.
|
||||||
|
account (str): The name of the account, can be empty.
|
||||||
|
|
||||||
|
Validation Logic:
|
||||||
|
- When 'account_id' is provided, 'asset', 'asset_id', and 'account' must not be provided.
|
||||||
|
- When 'account' is provided, either 'asset' or 'asset_id' must be provided.
|
||||||
|
- It is not allowed to provide both 'account_id' and 'asset_id' together.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the parameters do not meet the requirements, a detailed error message will be raised.
|
||||||
|
"""
|
||||||
|
def __init__(self, asset='', asset_id='', account='', account_id=''):
|
||||||
|
self.account_id = account_id
|
||||||
|
self.asset_id = asset_id
|
||||||
|
self.asset = asset
|
||||||
|
self.account = account
|
||||||
|
self.method = 'get'
|
||||||
|
self._init_check()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _valid_uuid(value):
|
||||||
|
if not value:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
uuid.UUID(str(value))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise ValueError('Invalid UUID: %s. Value must be a valid UUID.' % value)
|
||||||
|
|
||||||
|
def _init_check(self):
|
||||||
|
for id_value in [self.account_id, self.asset_id]:
|
||||||
|
self._valid_uuid(id_value)
|
||||||
|
|
||||||
|
if self.account_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.asset_id and not self.asset:
|
||||||
|
raise RequestParamsError(['asset', 'asset_id'])
|
||||||
|
|
||||||
|
if not self.account:
|
||||||
|
raise RequestParamsError(['account', 'account_id'])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_url():
|
||||||
|
return '/api/v1/accounts/service-integrations/account-secret/'
|
||||||
|
|
||||||
|
def get_query(self):
|
||||||
|
return {k: getattr(self, k) for k in vars(self) if getattr(self, k)}
|
||||||
|
|
||||||
|
|
||||||
|
class Secret(object):
|
||||||
|
def __init__(self, secret='', desc=''):
|
||||||
|
self.secret = secret
|
||||||
|
self.desc = desc
|
||||||
|
self.valid = not desc
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_exception(cls, e):
|
||||||
|
return cls(desc=str(e))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_response(cls, response):
|
||||||
|
secret, error = '', ''
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
if response.status_code != 200:
|
||||||
|
for k, v in data.items():
|
||||||
|
error += '%s: %s; ' % (k, v)
|
||||||
|
secret = data.get('secret')
|
||||||
|
except Exception as e:
|
||||||
|
error = str(e)
|
||||||
|
return cls(secret=secret, desc=error)
|
||||||
|
|
||||||
|
|
||||||
|
class JumpServerPAM(object):
|
||||||
|
def __init__(self, endpoint, key_id, key_secret, org_id=DEFAULT_ORG_ID):
|
||||||
|
self.endpoint = endpoint
|
||||||
|
self.key_id = key_id
|
||||||
|
self.key_secret = key_secret
|
||||||
|
self.org_id = org_id
|
||||||
|
self._auth = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headers(self):
|
||||||
|
gmt_form = '%a, %d %b %Y %H:%M:%S GMT'
|
||||||
|
return {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-JMS-ORG': self.org_id,
|
||||||
|
'Date': datetime.utcnow().strftime(gmt_form),
|
||||||
|
'X-Source': 'jms-pam'
|
||||||
|
}
|
||||||
|
|
||||||
|
def _build_url(self, url, query_params=None):
|
||||||
|
query_params = query_params or {}
|
||||||
|
endpoint = self.endpoint[:-1] if self.endpoint.endswith('/') else self.endpoint
|
||||||
|
return '%s%s?%s' % (endpoint, url, urlencode(query_params))
|
||||||
|
|
||||||
|
def _get_auth(self):
|
||||||
|
if self._auth is None:
|
||||||
|
signature_headers = ['(request-target)', 'accept', 'date']
|
||||||
|
self._auth = HTTPSignatureAuth(
|
||||||
|
key_id=self.key_id, secret=self.key_secret,
|
||||||
|
algorithm='hmac-sha256', headers=signature_headers
|
||||||
|
)
|
||||||
|
return self._auth
|
||||||
|
|
||||||
|
def send(self, secret_request):
|
||||||
|
try:
|
||||||
|
url = secret_request.get_url()
|
||||||
|
query_params = secret_request.get_query()
|
||||||
|
request_method = getattr(requests, secret_request.method)
|
||||||
|
response = request_method(
|
||||||
|
self._build_url(url, query_params),
|
||||||
|
auth=self._get_auth(), headers=self.headers
|
||||||
|
)
|
||||||
|
except RequestException as e:
|
||||||
|
return Secret.from_exception(e)
|
||||||
|
return Secret.from_response(response)
|
|
@ -0,0 +1,22 @@
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='jms-pam',
|
||||||
|
version='0.0.1',
|
||||||
|
packages=find_packages(),
|
||||||
|
install_requires=[
|
||||||
|
'requests',
|
||||||
|
'httpsig'
|
||||||
|
],
|
||||||
|
description='JumpServer PAM Client',
|
||||||
|
long_description=open('README.md').read(),
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
url='https://github.com/jumpserver',
|
||||||
|
author='JumpServer Team',
|
||||||
|
author_email='code@jumpserver.org',
|
||||||
|
classifiers=[
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
],
|
||||||
|
python_requires='>=3.6',
|
||||||
|
)
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-11-29 14:41
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
import common.db.utils
|
||||||
|
from django.db import migrations, models
|
||||||
|
import private_storage.fields
|
||||||
|
import private_storage.storage.files
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0014_gatheraccountsautomation_check_risk'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ServiceIntegration',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('logo_image', private_storage.fields.PrivateImageField(max_length=128, storage=private_storage.storage.files.PrivateFileSystemStorage(), upload_to='service-integration', verbose_name='Logo')),
|
||||||
|
('secret', common.db.fields.EncryptTextField(default='', verbose_name='Secret')),
|
||||||
|
('accounts', common.db.fields.JSONManyToManyField(default=dict, to='accounts.Account', verbose_name='Accounts')),
|
||||||
|
('ip_group', models.JSONField(default=common.db.utils.default_ip_group, verbose_name='IP group')),
|
||||||
|
('date_last_used', models.DateTimeField(blank=True, null=True, verbose_name='Date last used')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Service integration',
|
||||||
|
'unique_together': {('name', 'org_id')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -3,3 +3,4 @@ from .base import * # noqa
|
||||||
from .automations import * # noqa
|
from .automations import * # noqa
|
||||||
from .template import * # noqa
|
from .template import * # noqa
|
||||||
from .virtual import * # noqa
|
from .virtual import * # noqa
|
||||||
|
from .service import * # noqa
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from private_storage.fields import PrivateImageField
|
||||||
|
|
||||||
|
from accounts.models import Account
|
||||||
|
from common.db import fields
|
||||||
|
from common.db.fields import JSONManyToManyField, RelatedManager
|
||||||
|
from common.db.utils import default_ip_group
|
||||||
|
from common.utils import random_string
|
||||||
|
from orgs.mixins.models import JMSOrgBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceIntegration(JMSOrgBaseModel):
|
||||||
|
is_anonymous = False
|
||||||
|
|
||||||
|
name = models.CharField(max_length=128, unique=False, verbose_name=_('Name'))
|
||||||
|
logo_image = PrivateImageField(
|
||||||
|
upload_to='service-integration', max_length=128, verbose_name=_('Logo')
|
||||||
|
)
|
||||||
|
secret = fields.EncryptTextField(default='', verbose_name=_('Secret'))
|
||||||
|
accounts = JSONManyToManyField('accounts.Account', default=dict, verbose_name=_('Accounts'))
|
||||||
|
ip_group = models.JSONField(default=default_ip_group, verbose_name=_('IP group'))
|
||||||
|
date_last_used = models.DateTimeField(null=True, blank=True, verbose_name=_('Date last used'))
|
||||||
|
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = [('name', 'org_id')]
|
||||||
|
verbose_name = _('Service integration')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accounts_amount(self):
|
||||||
|
qs = Account.objects.all()
|
||||||
|
query = RelatedManager.get_to_filter_qs(self.accounts.value, Account)
|
||||||
|
return qs.filter(*query).count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
return self.is_active
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_authenticated(self):
|
||||||
|
return self.is_active
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def has_perms(perms):
|
||||||
|
support_perms = ['accounts.view_serviceintegration']
|
||||||
|
return all([perm in support_perms for perm in perms])
|
||||||
|
|
||||||
|
def get_secret(self):
|
||||||
|
self.secret = random_string(36)
|
||||||
|
self.save(update_fields=['secret'])
|
||||||
|
return self.secret
|
||||||
|
|
||||||
|
def get_account(self, asset='', asset_id='', account='', account_id=''):
|
||||||
|
qs = Account.objects.all()
|
||||||
|
if account_id:
|
||||||
|
qs = qs.filter(id=account_id)
|
||||||
|
elif account:
|
||||||
|
qs = qs.filter(name=account)
|
||||||
|
if asset_id:
|
||||||
|
qs = qs.filter(asset_id=asset_id)
|
||||||
|
elif asset:
|
||||||
|
qs = qs.filter(asset__name=asset)
|
||||||
|
query = RelatedManager.get_to_filter_qs(self.accounts.value, Account)
|
||||||
|
return qs.filter(*query).distinct().first()
|
|
@ -3,3 +3,4 @@ from .backup import *
|
||||||
from .base import *
|
from .base import *
|
||||||
from .template import *
|
from .template import *
|
||||||
from .virtual import *
|
from .virtual import *
|
||||||
|
from .service import *
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from accounts.models import ServiceIntegration
|
||||||
|
from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
|
||||||
|
from common.serializers.fields import JSONManyToManyField
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceIntegrationSerializer(serializers.ModelSerializer):
|
||||||
|
accounts = JSONManyToManyField(label=_('Account'))
|
||||||
|
ip_group = serializers.ListField(
|
||||||
|
default=['*'], label=_('Access IP'), help_text=ip_group_help_text,
|
||||||
|
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator])
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ServiceIntegration
|
||||||
|
fields_mini = ['id', 'name']
|
||||||
|
fields_small = fields_mini + ['logo_image', 'accounts']
|
||||||
|
fields = fields_small + [
|
||||||
|
'date_last_used', 'date_created', 'date_updated',
|
||||||
|
'ip_group', 'accounts_amount', 'comment', 'is_active'
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'comment': {'label': _('Comment')},
|
||||||
|
'name': {'label': _('Name')},
|
||||||
|
'is_active': {'default': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
request_method = self.context.get('request').method
|
||||||
|
if request_method == 'PUT':
|
||||||
|
self.fields['logo_image'].required = False
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceAccountSecretSerializer(serializers.Serializer):
|
||||||
|
asset = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
asset_id = serializers.UUIDField(required=False, allow_null=True)
|
||||||
|
account = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
account_id = serializers.UUIDField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _valid_at_least_one(attrs, fields):
|
||||||
|
if not any(attrs.get(field) for field in fields):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
f"At least one of the following fields must be provided: {', '.join(fields)}."
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
if attrs.get('account_id'):
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
self._valid_at_least_one(attrs, ['asset', 'asset_id'])
|
||||||
|
self._valid_at_least_one(attrs, ['account', 'account_id'])
|
||||||
|
return attrs
|
|
@ -9,6 +9,7 @@ app_name = 'accounts'
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
|
|
||||||
router.register(r'accounts', api.AccountViewSet, 'account')
|
router.register(r'accounts', api.AccountViewSet, 'account')
|
||||||
|
router.register(r'service-integrations', api.ServiceIntegrationViewSet, 'service-integration')
|
||||||
router.register(r'virtual-accounts', api.VirtualAccountViewSet, 'virtual-account')
|
router.register(r'virtual-accounts', api.VirtualAccountViewSet, 'virtual-account')
|
||||||
router.register(r'gathered-accounts', api.GatheredAccountViewSet, 'gathered-account')
|
router.register(r'gathered-accounts', api.GatheredAccountViewSet, 'gathered-account')
|
||||||
router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
|
router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
|
||||||
|
|
|
@ -33,13 +33,13 @@ from .const import ActivityChoices
|
||||||
from .filters import UserSessionFilterSet, OperateLogFilterSet
|
from .filters import UserSessionFilterSet, OperateLogFilterSet
|
||||||
from .models import (
|
from .models import (
|
||||||
FTPLog, UserLoginLog, OperateLog, PasswordChangeLog,
|
FTPLog, UserLoginLog, OperateLog, PasswordChangeLog,
|
||||||
ActivityLog, JobLog, UserSession
|
ActivityLog, JobLog, UserSession, ServiceAccessLog
|
||||||
)
|
)
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
FTPLogSerializer, UserLoginLogSerializer, JobLogSerializer,
|
FTPLogSerializer, UserLoginLogSerializer, JobLogSerializer,
|
||||||
OperateLogSerializer, OperateLogActionDetailSerializer,
|
OperateLogSerializer, OperateLogActionDetailSerializer,
|
||||||
PasswordChangeLogSerializer, ActivityUnionLogSerializer,
|
PasswordChangeLogSerializer, ActivityUnionLogSerializer,
|
||||||
FileSerializer, UserSessionSerializer
|
FileSerializer, UserSessionSerializer, ServiceAccessLogSerializer
|
||||||
)
|
)
|
||||||
from .utils import construct_userlogin_usernames
|
from .utils import construct_userlogin_usernames
|
||||||
|
|
||||||
|
@ -290,3 +290,15 @@ class UserSessionViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||||
user_session_manager.remove(key)
|
user_session_manager.remove(key)
|
||||||
queryset.delete()
|
queryset.delete()
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceAccessLogViewSet(OrgReadonlyModelViewSet):
|
||||||
|
model = ServiceAccessLog
|
||||||
|
serializer_class = ServiceAccessLogSerializer
|
||||||
|
extra_filter_backends = [DatetimeRangeFilterBackend]
|
||||||
|
date_range_filter_fields = [
|
||||||
|
('datetime', ('date_from', 'date_to'))
|
||||||
|
]
|
||||||
|
filterset_fields = ['account', 'remote_addr', 'service_id']
|
||||||
|
search_fields = filterset_fields
|
||||||
|
ordering = ['-datetime']
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-11-28 09:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('audits', '0003_auto_20180816_1652'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ServiceAccessLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('remote_addr', models.GenericIPAddressField(verbose_name='Remote addr')),
|
||||||
|
('service', models.CharField(max_length=128, verbose_name='Service')),
|
||||||
|
('service_id', models.UUIDField(verbose_name='Service ID')),
|
||||||
|
('asset', models.CharField(max_length=128, verbose_name='Asset')),
|
||||||
|
('account', models.CharField(max_length=128, verbose_name='Account')),
|
||||||
|
('datetime', models.DateTimeField(auto_now=True, verbose_name='Datetime')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -33,7 +33,8 @@ __all__ = [
|
||||||
"PasswordChangeLog",
|
"PasswordChangeLog",
|
||||||
"UserLoginLog",
|
"UserLoginLog",
|
||||||
"JobLog",
|
"JobLog",
|
||||||
"UserSession"
|
"UserSession",
|
||||||
|
"ServiceAccessLog",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -301,3 +302,13 @@ class UserSession(models.Model):
|
||||||
permissions = [
|
permissions = [
|
||||||
('offline_usersession', _('Offline user session')),
|
('offline_usersession', _('Offline user session')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceAccessLog(models.Model):
|
||||||
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
|
remote_addr = models.GenericIPAddressField(verbose_name=_("Remote addr"))
|
||||||
|
service = models.CharField(max_length=128, verbose_name=_("Service"))
|
||||||
|
service_id = models.UUIDField(verbose_name=_("Service ID"))
|
||||||
|
asset = models.CharField(max_length=128, verbose_name=_("Asset"))
|
||||||
|
account = models.CharField(max_length=128, verbose_name=_("Account"))
|
||||||
|
datetime = models.DateTimeField(auto_now=True, verbose_name=_("Datetime"))
|
||||||
|
|
|
@ -189,3 +189,19 @@ class UserSessionSerializer(serializers.ModelSerializer):
|
||||||
if not request:
|
if not request:
|
||||||
return False
|
return False
|
||||||
return request.session.session_key == obj.key
|
return request.session.session_key == obj.key
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceAccessLogSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.ServiceAccessLog
|
||||||
|
fields_mini = ['id']
|
||||||
|
fields_small = fields_mini + [
|
||||||
|
'remote_addr', 'service', 'service_id', 'asset', 'account', 'datetime'
|
||||||
|
]
|
||||||
|
fields = fields_small
|
||||||
|
extra_kwargs = {
|
||||||
|
'remote_addr': {'label': _('Remote Address')},
|
||||||
|
'asset': {'label': _('Asset')},
|
||||||
|
'account': {'label': _('Account')},
|
||||||
|
'datetime': {'label': _('Datetime')},
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password
|
||||||
router.register(r'job-logs', api.JobAuditViewSet, 'job-log')
|
router.register(r'job-logs', api.JobAuditViewSet, 'job-log')
|
||||||
router.register(r'my-login-logs', api.MyLoginLogViewSet, 'my-login-log')
|
router.register(r'my-login-logs', api.MyLoginLogViewSet, 'my-login-log')
|
||||||
router.register(r'user-sessions', api.UserSessionViewSet, 'user-session')
|
router.register(r'user-sessions', api.UserSessionViewSet, 'user-session')
|
||||||
|
router.register(r'service-access-logs', api.ServiceAccessLogViewSet, 'service-access-log')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('activities/', api.ResourceActivityAPIView.as_view(), name='resource-activities'),
|
path('activities/', api.ResourceActivityAPIView.as_view(), name='resource-activities'),
|
||||||
|
|
|
@ -7,9 +7,10 @@ from django.utils import timezone
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from rest_framework import authentication, exceptions
|
from rest_framework import authentication, exceptions
|
||||||
|
|
||||||
|
from accounts.models import ServiceIntegration
|
||||||
from common.auth import signature
|
from common.auth import signature
|
||||||
from common.decorators import merge_delay_run
|
from common.decorators import merge_delay_run
|
||||||
from common.utils import get_object_or_none, get_request_ip_or_data, contains_ip
|
from common.utils import get_object_or_none, get_request_ip_or_data, contains_ip, get_request_ip
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from ..models import AccessKey, PrivateToken
|
from ..models import AccessKey, PrivateToken
|
||||||
|
|
||||||
|
@ -33,6 +34,13 @@ def update_user_last_used(users=()):
|
||||||
User.objects.filter(id__in=users).update(date_api_key_last_used=timezone.now())
|
User.objects.filter(id__in=users).update(date_api_key_last_used=timezone.now())
|
||||||
|
|
||||||
|
|
||||||
|
@merge_delay_run(ttl=60)
|
||||||
|
def update_service_integration_last_used(service_integrations=()):
|
||||||
|
ServiceIntegration.objects.filter(
|
||||||
|
id__in=service_integrations
|
||||||
|
).update(date_last_used=timezone.now())
|
||||||
|
|
||||||
|
|
||||||
def after_authenticate_update_date(user, token=None):
|
def after_authenticate_update_date(user, token=None):
|
||||||
update_user_last_used.delay(users=(user.id,))
|
update_user_last_used.delay(users=(user.id,))
|
||||||
if token:
|
if token:
|
||||||
|
@ -146,3 +154,30 @@ class SignatureAuthentication(signature.SignatureAuthentication):
|
||||||
return True
|
return True
|
||||||
except (AccessKey.DoesNotExist, exceptions.ValidationError):
|
except (AccessKey.DoesNotExist, exceptions.ValidationError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceAuthentication(signature.SignatureAuthentication):
|
||||||
|
__instance = None
|
||||||
|
source = 'jms-pam'
|
||||||
|
|
||||||
|
def get_object(self, key_id):
|
||||||
|
if not self.__instance:
|
||||||
|
self.__instance = ServiceIntegration.objects.filter(
|
||||||
|
id=key_id, is_active=True,
|
||||||
|
).first()
|
||||||
|
return self.__instance
|
||||||
|
|
||||||
|
def fetch_user_data(self, key_id, algorithm=None):
|
||||||
|
obj = self.get_object(key_id)
|
||||||
|
if not obj:
|
||||||
|
return None, None
|
||||||
|
return obj, obj.secret
|
||||||
|
|
||||||
|
def is_ip_allow(self, key_id, request):
|
||||||
|
obj = self.get_object(key_id)
|
||||||
|
if not contains_ip(get_request_ip(request), obj.ip_group):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def after_authenticate_update_date(self, user):
|
||||||
|
update_service_integration_last_used.delay((user.id,))
|
||||||
|
|
|
@ -5,6 +5,8 @@ from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import common.db.models
|
import common.db.models
|
||||||
|
|
||||||
|
from common.db.utils import default_ip_group
|
||||||
from common.utils.random import random_string
|
from common.utils.random import random_string
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,10 +14,6 @@ def default_secret():
|
||||||
return random_string(36)
|
return random_string(36)
|
||||||
|
|
||||||
|
|
||||||
def default_ip_group():
|
|
||||||
return ["*"]
|
|
||||||
|
|
||||||
|
|
||||||
class AccessKey(models.Model):
|
class AccessKey(models.Model):
|
||||||
id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
secret = models.CharField(verbose_name='AccessKeySecret', default=default_secret, max_length=36)
|
secret = models.CharField(verbose_name='AccessKeySecret', default=default_secret, max_length=36)
|
||||||
|
|
|
@ -36,7 +36,7 @@ class SignatureAuthentication(authentication.BaseAuthentication):
|
||||||
:param www_authenticate_realm: Default: "api"
|
:param www_authenticate_realm: Default: "api"
|
||||||
:param required_headers: Default: ["(request-target)", "date"]
|
:param required_headers: Default: ["(request-target)", "date"]
|
||||||
"""
|
"""
|
||||||
|
source = ''
|
||||||
www_authenticate_realm = "api"
|
www_authenticate_realm = "api"
|
||||||
required_headers = ["(request-target)", "date"]
|
required_headers = ["(request-target)", "date"]
|
||||||
|
|
||||||
|
@ -47,6 +47,9 @@ class SignatureAuthentication(authentication.BaseAuthentication):
|
||||||
def is_ip_allow(self, key_id, request):
|
def is_ip_allow(self, key_id, request):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def after_authenticate_update_date(self, user):
|
||||||
|
pass
|
||||||
|
|
||||||
def authenticate_header(self, request):
|
def authenticate_header(self, request):
|
||||||
"""
|
"""
|
||||||
DRF sends this for unauthenticated responses if we're the primary
|
DRF sends this for unauthenticated responses if we're the primary
|
||||||
|
@ -74,6 +77,9 @@ class SignatureAuthentication(authentication.BaseAuthentication):
|
||||||
if method.lower() != 'signature':
|
if method.lower() != 'signature':
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if self.source and request.META.get('HTTP_X_SOURCE') != self.source:
|
||||||
|
return None
|
||||||
|
|
||||||
# Verify basic header structure.
|
# Verify basic header structure.
|
||||||
if len(fields) == 0:
|
if len(fields) == 0:
|
||||||
raise FAILED
|
raise FAILED
|
||||||
|
@ -117,4 +123,5 @@ class SignatureAuthentication(authentication.BaseAuthentication):
|
||||||
if not hs.verify():
|
if not hs.verify():
|
||||||
raise FAILED
|
raise FAILED
|
||||||
|
|
||||||
|
self.after_authenticate_update_date(user)
|
||||||
return user, fields["keyid"]
|
return user, fields["keyid"]
|
||||||
|
|
|
@ -39,6 +39,7 @@ __all__ = [
|
||||||
"BitChoices",
|
"BitChoices",
|
||||||
"TreeChoices",
|
"TreeChoices",
|
||||||
"JSONManyToManyField",
|
"JSONManyToManyField",
|
||||||
|
"RelatedManager",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,10 @@ from common.utils import get_logger, signer, crypto
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def default_ip_group():
|
||||||
|
return ["*"]
|
||||||
|
|
||||||
|
|
||||||
def get_object_if_need(model, pk):
|
def get_object_if_need(model, pk):
|
||||||
if not isinstance(pk, model):
|
if not isinstance(pk, model):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -25,7 +25,7 @@ __all__ = [
|
||||||
'IDInFilterBackend', "CustomFilterBackend",
|
'IDInFilterBackend', "CustomFilterBackend",
|
||||||
"BaseFilterSet", 'IDNotFilterBackend',
|
"BaseFilterSet", 'IDNotFilterBackend',
|
||||||
'NotOrRelFilterBackend', 'LabelFilterBackend',
|
'NotOrRelFilterBackend', 'LabelFilterBackend',
|
||||||
'RewriteOrderingFilter'
|
'RewriteOrderingFilter', 'AttrRulesFilterBackend'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -297,6 +297,7 @@ class JSONManyToManyField(serializers.JSONField):
|
||||||
if not data:
|
if not data:
|
||||||
data = {}
|
data = {}
|
||||||
try:
|
try:
|
||||||
|
data = super().to_internal_value(data)
|
||||||
ModelJSONManyToManyField.check_value(data)
|
ModelJSONManyToManyField.check_value(data)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise serializers.ValidationError(e)
|
raise serializers.ValidationError(e)
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"AccessIP": "IP whitelist",
|
"AccessIP": "IP whitelist",
|
||||||
"AccessKey": "Access key",
|
"AccessKey": "Access key",
|
||||||
"Account": "Account",
|
"Account": "Account",
|
||||||
|
"AccountAmount": "Account amount",
|
||||||
"AccountBackup": "Backup accounts",
|
"AccountBackup": "Backup accounts",
|
||||||
"AccountBackupCreate": "Create account backup",
|
"AccountBackupCreate": "Create account backup",
|
||||||
"AccountBackupDetail": "Backup account details",
|
"AccountBackupDetail": "Backup account details",
|
||||||
|
@ -241,6 +242,7 @@
|
||||||
"CAS": "CAS",
|
"CAS": "CAS",
|
||||||
"CMPP2": "Cmpp v2.0",
|
"CMPP2": "Cmpp v2.0",
|
||||||
"CalculationResults": "Error in cron expression",
|
"CalculationResults": "Error in cron expression",
|
||||||
|
"CallRecords": "Call Records",
|
||||||
"CanDragSelect": "Select time period by dragging mouse;No selection means all selected",
|
"CanDragSelect": "Select time period by dragging mouse;No selection means all selected",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"CancelCollection": "Cancel favorite",
|
"CancelCollection": "Cancel favorite",
|
||||||
|
@ -403,6 +405,7 @@
|
||||||
"DateLastLogin": "Last login date",
|
"DateLastLogin": "Last login date",
|
||||||
"DateLastMonth": "Last month",
|
"DateLastMonth": "Last month",
|
||||||
"DateLastSync": "Last sync",
|
"DateLastSync": "Last sync",
|
||||||
|
"DataLastUsed": "Last used",
|
||||||
"DateLastWeek": "Last week",
|
"DateLastWeek": "Last week",
|
||||||
"DateLastYear": "Last year",
|
"DateLastYear": "Last year",
|
||||||
"DatePasswordLastUpdated": "Last password update date",
|
"DatePasswordLastUpdated": "Last password update date",
|
||||||
|
@ -1083,6 +1086,7 @@
|
||||||
"ServerAccountKey": "Service account key",
|
"ServerAccountKey": "Service account key",
|
||||||
"ServerError": "Server error",
|
"ServerError": "Server error",
|
||||||
"ServerTime": "Server time",
|
"ServerTime": "Server time",
|
||||||
|
"ServiceIntegration": "Service integration",
|
||||||
"Session": "Session",
|
"Session": "Session",
|
||||||
"SessionCommands": "Session commands",
|
"SessionCommands": "Session commands",
|
||||||
"SessionConnectTrend": "Session connection trends",
|
"SessionConnectTrend": "Session connection trends",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"AccessIP": "IP ホワイトリスト",
|
"AccessIP": "IP ホワイトリスト",
|
||||||
"AccessKey": "アクセスキー",
|
"AccessKey": "アクセスキー",
|
||||||
"Account": "アカウント情報",
|
"Account": "アカウント情報",
|
||||||
|
"AccountAmount": "アカウント数",
|
||||||
"AccountBackup": "アカウントのバックアップ",
|
"AccountBackup": "アカウントのバックアップ",
|
||||||
"AccountBackupCreate": "アカウントバックアップを作成",
|
"AccountBackupCreate": "アカウントバックアップを作成",
|
||||||
"AccountBackupDetail": "アカウントバックアップの詳細",
|
"AccountBackupDetail": "アカウントバックアップの詳細",
|
||||||
|
@ -254,6 +255,7 @@
|
||||||
"CMPP2": "CMPP v2.0",
|
"CMPP2": "CMPP v2.0",
|
||||||
"CTYunPrivate": "天翼プライベートクラウド",
|
"CTYunPrivate": "天翼プライベートクラウド",
|
||||||
"CalculationResults": "cron 式のエラー",
|
"CalculationResults": "cron 式のエラー",
|
||||||
|
"CallRecords": "つうわきろく",
|
||||||
"CanDragSelect": "マウスドラッグで時間帯を選択可能;未選択は全選択と同じです",
|
"CanDragSelect": "マウスドラッグで時間帯を選択可能;未選択は全選択と同じです",
|
||||||
"Cancel": "キャンセル",
|
"Cancel": "キャンセル",
|
||||||
"CancelCollection": "お気に入りキャンセル",
|
"CancelCollection": "お気に入りキャンセル",
|
||||||
|
@ -418,6 +420,7 @@
|
||||||
"DateLastLogin": "最後にログインした日",
|
"DateLastLogin": "最後にログインした日",
|
||||||
"DateLastMonth": "最近一ヶ月",
|
"DateLastMonth": "最近一ヶ月",
|
||||||
"DateLastSync": "最終同期日",
|
"DateLastSync": "最終同期日",
|
||||||
|
"DataLastUsed": "さいごしようび",
|
||||||
"DateLastWeek": "最新の一週間",
|
"DateLastWeek": "最新の一週間",
|
||||||
"DateLastYear": "最近一年",
|
"DateLastYear": "最近一年",
|
||||||
"DatePasswordLastUpdated": "最終パスワード更新日",
|
"DatePasswordLastUpdated": "最終パスワード更新日",
|
||||||
|
@ -1118,6 +1121,7 @@
|
||||||
"ServerAccountKey": "サービスアカウントキー",
|
"ServerAccountKey": "サービスアカウントキー",
|
||||||
"ServerError": "サーバーエラー",
|
"ServerError": "サーバーエラー",
|
||||||
"ServerTime": "サーバータイム",
|
"ServerTime": "サーバータイム",
|
||||||
|
"ServiceIntegration": "サービス統合",
|
||||||
"Session": "コンバセーション",
|
"Session": "コンバセーション",
|
||||||
"SessionCommands": "セッションアクション",
|
"SessionCommands": "セッションアクション",
|
||||||
"SessionConnectTrend": "セッションの接続トレンド",
|
"SessionConnectTrend": "セッションの接続トレンド",
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"Accept": "同意",
|
"Accept": "同意",
|
||||||
"AccessIP": "IP 白名单",
|
"AccessIP": "IP 白名单",
|
||||||
"AccessKey": "访问密钥",
|
"AccessKey": "访问密钥",
|
||||||
|
"AccountAmount": "账号数量",
|
||||||
"AccountBackup": "账号备份",
|
"AccountBackup": "账号备份",
|
||||||
"AccountBackupCreate": "创建账号备份",
|
"AccountBackupCreate": "创建账号备份",
|
||||||
"AccountBackupDetail": "账号备份详情",
|
"AccountBackupDetail": "账号备份详情",
|
||||||
|
@ -241,6 +242,7 @@
|
||||||
"CAS": "CAS",
|
"CAS": "CAS",
|
||||||
"CMPP2": "CMPP v2.0",
|
"CMPP2": "CMPP v2.0",
|
||||||
"CalculationResults": "cron 表达式错误",
|
"CalculationResults": "cron 表达式错误",
|
||||||
|
"CallRecords": "调用记录",
|
||||||
"CanDragSelect": "可拖动鼠标选择时间段;未选择等同全选",
|
"CanDragSelect": "可拖动鼠标选择时间段;未选择等同全选",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
"CancelCollection": "取消收藏",
|
"CancelCollection": "取消收藏",
|
||||||
|
@ -403,6 +405,7 @@
|
||||||
"DateLastLogin": "最后登录日期",
|
"DateLastLogin": "最后登录日期",
|
||||||
"DateLastMonth": "最近一月",
|
"DateLastMonth": "最近一月",
|
||||||
"DateLastSync": "最后同步日期",
|
"DateLastSync": "最后同步日期",
|
||||||
|
"DataLastUsed": "最后使用日期",
|
||||||
"DateLastWeek": "最近一周",
|
"DateLastWeek": "最近一周",
|
||||||
"DateLastYear": "最近一年",
|
"DateLastYear": "最近一年",
|
||||||
"DatePasswordLastUpdated": "最后更新密码日期",
|
"DatePasswordLastUpdated": "最后更新密码日期",
|
||||||
|
@ -1086,6 +1089,7 @@
|
||||||
"ServerAccountKey": "服务账号密钥",
|
"ServerAccountKey": "服务账号密钥",
|
||||||
"ServerError": "服务器错误",
|
"ServerError": "服务器错误",
|
||||||
"ServerTime": "服务器时间",
|
"ServerTime": "服务器时间",
|
||||||
|
"ServiceIntegration": "服务对接",
|
||||||
"Session": "会话",
|
"Session": "会话",
|
||||||
"SessionCommands": "会话命令",
|
"SessionCommands": "会话命令",
|
||||||
"SessionConnectTrend": "会话连接趋势",
|
"SessionConnectTrend": "会话连接趋势",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"AccessIP": "IP 白名單",
|
"AccessIP": "IP 白名單",
|
||||||
"AccessKey": "訪問金鑰",
|
"AccessKey": "訪問金鑰",
|
||||||
"Account": "雲帳號",
|
"Account": "雲帳號",
|
||||||
|
"AccountAmount": "帳號數量",
|
||||||
"AccountBackup": "帳號備份",
|
"AccountBackup": "帳號備份",
|
||||||
"AccountBackupCreate": "創建帳號備份",
|
"AccountBackupCreate": "創建帳號備份",
|
||||||
"AccountBackupDetail": "賬號備份詳情",
|
"AccountBackupDetail": "賬號備份詳情",
|
||||||
|
@ -324,7 +325,8 @@
|
||||||
"CASSetting": "CAS 配置",
|
"CASSetting": "CAS 配置",
|
||||||
"CMPP2": "CMPP v2.0",
|
"CMPP2": "CMPP v2.0",
|
||||||
"CTYunPrivate": "天翼私有雲",
|
"CTYunPrivate": "天翼私有雲",
|
||||||
"CalculationResults": "Cron expression error",
|
"CalculationResults": "呼叫記錄",
|
||||||
|
"CallRecords": "調用記錄",
|
||||||
"CanDragSelect": "可拖動滑鼠選擇時間段;未選擇等同全選",
|
"CanDragSelect": "可拖動滑鼠選擇時間段;未選擇等同全選",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
"CancelCollection": "取消收藏",
|
"CancelCollection": "取消收藏",
|
||||||
|
@ -534,6 +536,7 @@
|
||||||
"DateLastMonth": "最近一月",
|
"DateLastMonth": "最近一月",
|
||||||
"DateLastRun": "上次運行日期",
|
"DateLastRun": "上次運行日期",
|
||||||
"DateLastSync": "最後同步日期",
|
"DateLastSync": "最後同步日期",
|
||||||
|
"DataLastUsed": "最後使用日期",
|
||||||
"DateLastWeek": "最近一週",
|
"DateLastWeek": "最近一週",
|
||||||
"DateLastYear": "最近一年",
|
"DateLastYear": "最近一年",
|
||||||
"DatePasswordLastUpdated": "最後更新密碼日期",
|
"DatePasswordLastUpdated": "最後更新密碼日期",
|
||||||
|
@ -1436,6 +1439,7 @@
|
||||||
"ServerAccountKey": "服務帳號金鑰",
|
"ServerAccountKey": "服務帳號金鑰",
|
||||||
"ServerError": "伺服器錯誤",
|
"ServerError": "伺服器錯誤",
|
||||||
"ServerTime": "伺服器時間",
|
"ServerTime": "伺服器時間",
|
||||||
|
"ServiceIntegration": "服務對接",
|
||||||
"ServiceRatio": "組件負載統計",
|
"ServiceRatio": "組件負載統計",
|
||||||
"Session": "會話",
|
"Session": "會話",
|
||||||
"SessionCommands": "會話指令",
|
"SessionCommands": "會話指令",
|
||||||
|
|
|
@ -32,6 +32,7 @@ REST_FRAMEWORK = {
|
||||||
# 'rest_framework.authentication.BasicAuthentication',
|
# 'rest_framework.authentication.BasicAuthentication',
|
||||||
'authentication.backends.drf.AccessTokenAuthentication',
|
'authentication.backends.drf.AccessTokenAuthentication',
|
||||||
'authentication.backends.drf.PrivateTokenAuthentication',
|
'authentication.backends.drf.PrivateTokenAuthentication',
|
||||||
|
'authentication.backends.drf.ServiceAuthentication',
|
||||||
'authentication.backends.drf.SignatureAuthentication',
|
'authentication.backends.drf.SignatureAuthentication',
|
||||||
'authentication.backends.drf.SessionAuthentication',
|
'authentication.backends.drf.SessionAuthentication',
|
||||||
),
|
),
|
||||||
|
|
|
@ -29,7 +29,8 @@ __all__ = [
|
||||||
"User",
|
"User",
|
||||||
"UserPasswordHistory",
|
"UserPasswordHistory",
|
||||||
"MFAMixin",
|
"MFAMixin",
|
||||||
"AuthMixin"
|
"AuthMixin",
|
||||||
|
"RoleMixin"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue