web_sandbox_console 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +71 -9
- data/app/assets/stylesheets/web_sandbox_console/application.css +57 -3
- data/app/assets/stylesheets/web_sandbox_console/home.css +1 -1
- data/app/controllers/web_sandbox_console/authorization_controller.rb +82 -0
- data/app/controllers/web_sandbox_console/home_controller.rb +10 -2
- data/app/views/layouts/web_sandbox_console/application.html.erb +7 -0
- data/app/views/web_sandbox_console/authorization/auth_page.html.erb +35 -0
- data/app/views/web_sandbox_console/authorization/fetch_token.js.erb +3 -0
- data/app/views/web_sandbox_console/home/do_view_file.js.erb +4 -2
- data/app/views/web_sandbox_console/home/eval_code.js.erb +1 -1
- data/app/views/web_sandbox_console/home/index.html.erb +20 -5
- data/app/views/web_sandbox_console/home/view_file.html.erb +15 -0
- data/config/routes.rb +6 -0
- data/lib/generators/web_sandbox_console/templates/web_sandbox_console.rb +7 -1
- data/lib/web_sandbox_console/configuration.rb +6 -1
- data/lib/web_sandbox_console/sandbox.rb +38 -7
- data/lib/web_sandbox_console/version.rb +1 -1
- data/lib/web_sandbox_console/view_file.rb +62 -10
- metadata +5 -3
- data/app/jobs/web_sandbox_console/application_job.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4c806cc25ee28c518675afdae02525a3c36f201b4e7d1e282b0924afba91725
|
4
|
+
data.tar.gz: f6cbacc2cea644ca0baf438b2c8080e92ef9b6d28ab10305c0cf897beac04723
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ac31b27cb734fe08a57be44e8295f03497a95bfb7720881349497a89b5465ffd39a86700d8a4b2fee2a00c4da318da13e514fe96d646d53572abe80edd682c2
|
7
|
+
data.tar.gz: 5c3d3ec3910f929ffba5f6bca090e55752f3c82c162b22ceeb1b7a48c91b321d5d00076602cb8aca45715f8cdafbc4377d2e0db5a59ed4759085798cfe4a984a
|
data/README.md
CHANGED
@@ -18,17 +18,22 @@ gem 'web_sandbox_console'
|
|
18
18
|
$ bundle
|
19
19
|
```
|
20
20
|
|
21
|
-
此时,如果是在本地,你访问 `http://localhost:3000/web_sandbox_console` 就能看到web
|
21
|
+
此时,如果是在本地,你访问 `http://localhost:3000/web_sandbox_console` 就能看到web控制台了,下面这个样子。
|
22
|
+

|
22
23
|
|
23
24
|
## 配置
|
24
|
-
|
25
|
+
```
|
26
|
+
在bundle后,就可以使用一些基础的功能了;
|
27
|
+
如果你不满足基础的功能、或者需要更高的安全性,很有必要仔细了解配置选项,总之还是很推荐对项目进行适当配置;
|
28
|
+
关于配置的详细介绍,在生成的文件中也有详细的说明,参照说明配置即可;
|
29
|
+
```
|
25
30
|
|
26
31
|
在 rails 项目路径下,执行:
|
27
32
|
```bash
|
28
33
|
$ rails g web_sandbox_console
|
29
34
|
```
|
30
35
|
|
31
|
-
|
36
|
+
这会在项目路径下,创建如下文件(即配置文件)
|
32
37
|
```ruby
|
33
38
|
# config/initializers/web_sandbox_console.rb
|
34
39
|
|
@@ -50,22 +55,79 @@ WebSandboxConsole.setup do |config|
|
|
50
55
|
# # 配置 黑名单 实例方法
|
51
56
|
# config.instance_method_blacklist = {Kernel: %i(system exec `),File: %i(chmod chown)}
|
52
57
|
|
53
|
-
# 文件黑名单列表 (如果是目录
|
58
|
+
# 文件黑名单列表 (如果是目录 则目录下所有文件都不可用)目录以 / 结尾
|
54
59
|
# 默认都是项目路径下的
|
55
60
|
# config.view_file_blacklist = %w(config/secrets.yml vendor/)
|
56
61
|
|
62
|
+
# 配置 文件权限,是否仅能查看log文件,默开启
|
63
|
+
#config.only_view_log_file = false
|
64
|
+
|
65
|
+
# 通过非对称加密方式 升级权限,授权通过后,可获得执行数据权限(PS: 数据操作不再回滚)
|
66
|
+
# config.public_key = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMbJOE1vQT1jFpaH1GPYzdRJN/\nLh8VePmzXs5BYOLHB0xIjArL1NlXMbCJ+AS2rv3/oHIOdHhEuZw0tmm9DhG100R8\nRjBpsEKCDI88jl9qRkFmD3CVk8XQXv6c2IkRZCYSTvgDkmnKAlORksfw+p0cR2AQ\nlAtAsNsNviKYBzXKfQIDAQAB\n-----END PUBLIC KEY-----\n"
|
67
|
+
|
57
68
|
# # 配置 日志路径 默认路径位于项目下
|
58
69
|
# config.console_log_path = "log/web_sandbox_console.log"
|
59
70
|
end
|
60
71
|
```
|
61
72
|
|
62
|
-
##
|
63
|
-
|
64
|
-
|
73
|
+
## 深入了解
|
74
|
+
主要包含两大功能快:代码执行、文件查看,下面分别介绍
|
75
|
+
|
76
|
+
### 代码执行
|
77
|
+
1. 提交和异步执行
|
78
|
+
|
79
|
+
> 提交后代码会立即执行;如果点击异步执行,则代码会在后台异步执行,这对于需要执行非常耗时的代码,强烈建议异步执行;
|
80
|
+
|
81
|
+
2. 升级权限
|
82
|
+

|
83
|
+
```
|
84
|
+
通常你能做的操作就是,查查数据等,数据的所有操作都不会写入到数据库,这样很安全;但是每当你需要修改数据时,还是需要让运维处理;为了满足可以数据写入、和安全性的要求;开发了升级权限这个功能。
|
85
|
+
|
86
|
+
升级权限你需要在配置文件中配置公钥,自己保存私匙;整个过程采用非对称加密的方式,进行授权,还是比较安全的。
|
87
|
+
升级授权成功后,代码执行将不再回滚,会直接写入数据库。
|
88
|
+
```
|
89
|
+
|
90
|
+
升级权限流程如下:
|
91
|
+
> - `config/initializers/web_sandbox_console.rb`配置公钥
|
92
|
+
> - 进入授权页面,点击获取令牌 `web_sandbox_console/auth_page`
|
93
|
+
> - 用私钥对令牌加密,然后用base64加密
|
94
|
+
> - 将加密的打印结果(注意是puts 文本),输入加密密文框,提交
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# 本地生成 加密密文代码
|
98
|
+
require 'openssl'
|
99
|
+
require 'base64'
|
100
|
+
|
101
|
+
private_key = "你的私钥"
|
102
|
+
p_key = OpenSSL::PKey::RSA.new private_key
|
103
|
+
secret_text = p_key.private_encrypt("你的令牌")
|
104
|
+
encode_text = Base64.encode64(secret_text)
|
105
|
+
puts encode_text
|
106
|
+
```
|
107
|
+
|
108
|
+
### 文件查看
|
109
|
+

|
110
|
+
1. 目录和文件
|
111
|
+
|
112
|
+
> 你可以查看一个目录下有哪些文件夹或文件,你也可以直接查看文件的内容,默认返回一个文件的前100行
|
113
|
+
|
114
|
+
2. 指定行数
|
115
|
+
```
|
116
|
+
你可以根据文件总行数,指定查看文件的开始行数、结束行数。
|
117
|
+
PS: a. 在过滤文件(过滤内容/过滤时间)的时候,此时指定行数将被忽略
|
118
|
+
b. 对于大文件(默认超过10M),处于性能考虑,此时指定行数也会被忽略
|
119
|
+
```
|
120
|
+
|
121
|
+
3. 过滤
|
122
|
+
```
|
123
|
+
需要特别说明的是,过滤时间是针对日志文件写的,因此如果是非日志文件,将不会有任何效果。
|
124
|
+
建议:出于性能考虑,过滤时候尽量缩小时间范围
|
125
|
+
```
|
126
|
+
|
127
|
+
4. 权限
|
65
128
|
|
66
|
-
|
129
|
+
> 默认情况,只能查看日志文件或目录,当然你也可以去配置做调整。
|
67
130
|
|
68
|
-

|
69
131
|
## Contributing
|
70
132
|
Contribution directions go here.
|
71
133
|
|
@@ -18,6 +18,47 @@
|
|
18
18
|
line-height: 18px;
|
19
19
|
}
|
20
20
|
|
21
|
+
.tip {
|
22
|
+
color: red;
|
23
|
+
font-size: 12px;
|
24
|
+
}
|
25
|
+
|
26
|
+
.fr {
|
27
|
+
float: right;
|
28
|
+
}
|
29
|
+
|
30
|
+
.fl {
|
31
|
+
float: left;
|
32
|
+
}
|
33
|
+
|
34
|
+
.h250{
|
35
|
+
height: 250px;
|
36
|
+
}
|
37
|
+
|
38
|
+
.margin-t-10{
|
39
|
+
margin-top: 10px;
|
40
|
+
}
|
41
|
+
|
42
|
+
.margin-t-100{
|
43
|
+
margin-top: 100px;
|
44
|
+
}
|
45
|
+
|
46
|
+
.margin-b-20{
|
47
|
+
margin-bottom: 20px;
|
48
|
+
}
|
49
|
+
|
50
|
+
.text_area_div {
|
51
|
+
height: 250px;
|
52
|
+
width: 88%;
|
53
|
+
display: inline-block;
|
54
|
+
}
|
55
|
+
|
56
|
+
.submit_div {
|
57
|
+
height: 250px;
|
58
|
+
width: 9%;
|
59
|
+
display: inline-block;
|
60
|
+
}
|
61
|
+
|
21
62
|
.container{
|
22
63
|
margin: 20px 15px;
|
23
64
|
}
|
@@ -35,9 +76,10 @@
|
|
35
76
|
|
36
77
|
.text_area_box{
|
37
78
|
font-size: 15px;
|
38
|
-
height:
|
39
|
-
width:
|
79
|
+
height: 100%;
|
80
|
+
width: 95%;
|
40
81
|
border-width: 2px;
|
82
|
+
overflow: auto;
|
41
83
|
}
|
42
84
|
|
43
85
|
.clear-button{
|
@@ -48,6 +90,7 @@
|
|
48
90
|
color: white;
|
49
91
|
cursor: pointer;
|
50
92
|
border-radius: 5px;
|
93
|
+
border: red;
|
51
94
|
}
|
52
95
|
|
53
96
|
.send-button{
|
@@ -66,4 +109,15 @@
|
|
66
109
|
font-size: 15px;
|
67
110
|
color: 15px;
|
68
111
|
cursor: pointer;
|
69
|
-
|
112
|
+
border: red;
|
113
|
+
}
|
114
|
+
|
115
|
+
button,input{
|
116
|
+
outline:none;
|
117
|
+
}
|
118
|
+
|
119
|
+
button:focus,input:focus,textarea:focus {
|
120
|
+
outline: none;
|
121
|
+
border: 1px solid #272822;
|
122
|
+
}
|
123
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_dependency "web_sandbox_console/application_controller"
|
2
|
+
|
3
|
+
module WebSandboxConsole
|
4
|
+
class AuthorizationController < ApplicationController
|
5
|
+
before_action :restrict_ip
|
6
|
+
http_basic_authenticate_with name: WebSandboxConsole.http_basic_auth[:name], password: WebSandboxConsole.http_basic_auth[:password] if WebSandboxConsole.http_basic_auth.present?
|
7
|
+
before_action :restrict_fetch_token_times, only: :fetch_token
|
8
|
+
|
9
|
+
# 获取令牌
|
10
|
+
def fetch_token
|
11
|
+
@token = SecureRandom.uuid
|
12
|
+
save_cache_token(@token)
|
13
|
+
end
|
14
|
+
|
15
|
+
# 授权
|
16
|
+
def auth
|
17
|
+
if params[:secret_text].blank?
|
18
|
+
flash[:notice] = '密文为空'
|
19
|
+
return redirect_to root_path
|
20
|
+
end
|
21
|
+
|
22
|
+
if public_key.blank?
|
23
|
+
flash[:notice] = '公钥未配置'
|
24
|
+
return redirect_to root_path
|
25
|
+
end
|
26
|
+
|
27
|
+
result_hash = decrypt_secret_text(params[:secret_text])
|
28
|
+
token = result_hash[:content]
|
29
|
+
if result_hash[:success] && fetch_cache_token(token)
|
30
|
+
flash[:notice] = "授权成功"
|
31
|
+
session[:pass_auth] = true
|
32
|
+
else
|
33
|
+
flash[:notice] = "授权失败:#{result_hash[:content]}"
|
34
|
+
end
|
35
|
+
|
36
|
+
redirect_to root_path
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
# 限制获取token次数 一天内不允许超过20次
|
41
|
+
def restrict_fetch_token_times
|
42
|
+
cache = Rails.cache
|
43
|
+
times = cache.fetch('fetch_token_times', expires_in: 1.day) {0}
|
44
|
+
|
45
|
+
if times > 20
|
46
|
+
flash[:notice] = '一天内获取令牌不允许超过20次'
|
47
|
+
rediect_to root_path
|
48
|
+
end
|
49
|
+
cache.write('fetch_token_times', times + 1)
|
50
|
+
end
|
51
|
+
|
52
|
+
# 保存token 到缓存
|
53
|
+
def save_cache_token(key, value = nil)
|
54
|
+
Rails.cache.write(key.to_s, value.presence || key.to_s, expires_in: 5.minutes)
|
55
|
+
end
|
56
|
+
|
57
|
+
# 获取 缓存中 token
|
58
|
+
def fetch_cache_token(key)
|
59
|
+
Rails.cache.read(key.to_s)
|
60
|
+
end
|
61
|
+
|
62
|
+
# 公钥
|
63
|
+
def public_key
|
64
|
+
WebSandboxConsole.public_key
|
65
|
+
end
|
66
|
+
|
67
|
+
# 解密
|
68
|
+
def decrypt_secret_text(secret_text)
|
69
|
+
begin
|
70
|
+
base_text = Base64.decode64(secret_text)
|
71
|
+
p_key = OpenSSL::PKey::RSA.new public_key
|
72
|
+
text = p_key.public_decrypt(base_text)
|
73
|
+
{success: true, content: text}
|
74
|
+
rescue OpenSSL::PKey::RSAError
|
75
|
+
{success: false, content: "密钥匹配失败"}
|
76
|
+
rescue Exception => e
|
77
|
+
{success: false, content: "发生未知错误: #{e.inspect};#{e.backtrace[0..2].join('\r\n')}"}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -10,7 +10,13 @@ module WebSandboxConsole
|
|
10
10
|
|
11
11
|
# 执行代码
|
12
12
|
def eval_code
|
13
|
-
|
13
|
+
sandbox = Sandbox.new(params[:code], session[:pass_auth])
|
14
|
+
|
15
|
+
@results = if params[:commit] == '异步执行'
|
16
|
+
sandbox.asyn_evalotor
|
17
|
+
else
|
18
|
+
sandbox.evalotor
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
16
22
|
def view_file
|
@@ -18,7 +24,9 @@ module WebSandboxConsole
|
|
18
24
|
|
19
25
|
# 查看文件
|
20
26
|
def do_view_file
|
21
|
-
|
27
|
+
results = ViewFile.new(params).view
|
28
|
+
@lines = results[:lines]
|
29
|
+
@total_line_num = results[:total_line_num]
|
22
30
|
end
|
23
31
|
end
|
24
32
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# 授权
|
2
|
+
<p>
|
3
|
+
<%= link_to '控制台', web_sandbox_console.root_path %>
|
4
|
+
<%= link_to '获取令牌', web_sandbox_console.fetch_token_path(:format => "js"), remote: true %>
|
5
|
+
<span> PS: 注意获取令牌一天最多20次,有效期仅5分钟</span>
|
6
|
+
</p>
|
7
|
+
|
8
|
+
|
9
|
+
<h2>授权申请</h1>
|
10
|
+
<div class="">
|
11
|
+
<%= form_tag web_sandbox_console.auth_path, method: :post do %>
|
12
|
+
<table>
|
13
|
+
<tr>
|
14
|
+
<td>加密密文</td>
|
15
|
+
<td>
|
16
|
+
<%= text_area_tag :secret_text, '', style: "width: 300px;height: 100px;" %>
|
17
|
+
</td>
|
18
|
+
<td><%= submit_tag '提交', data: { disable_with: "已发送,处理中..." }, class: 'view-file-button' %></td>
|
19
|
+
</table>
|
20
|
+
<% end %>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<h4>说明令牌使用说明:</h4>
|
24
|
+
<div>
|
25
|
+
<pre>
|
26
|
+
require 'openssl'
|
27
|
+
require 'base64'
|
28
|
+
|
29
|
+
private_key = "你的私钥"
|
30
|
+
p_key = OpenSSL::PKey::RSA.new private_key
|
31
|
+
secret_text = p_key.private_encrypt("你的令牌")
|
32
|
+
encode_text = Base64.encode64(secret_text)
|
33
|
+
puts encode_text
|
34
|
+
</pre>
|
35
|
+
</div>
|
@@ -1,5 +1,7 @@
|
|
1
1
|
$(".output-content").empty();
|
2
|
-
|
3
|
-
|
2
|
+
<% line_num_show = @total_line_num ? "原始文件总行数:#{@total_line_num}" : "" %>
|
3
|
+
|
4
|
+
$(".output-content").append("<p>============ <%= Time.current %> 查找结果如下 <%= line_num_show %> ===============</p>");
|
5
|
+
<% @lines.each do |result| %>
|
4
6
|
$(".output-content").append('<pre><%= escape_javascript result %></pre>');
|
5
7
|
<% end %>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
$(".output-content").append(
|
1
|
+
$(".output-content").append("<p>============ <%= Time.current %> 本次输出如下 ===============</p>");
|
2
2
|
<% @results.each do |result| %>
|
3
3
|
$(".output-content").append('<p><%= escape_javascript result %></p>');
|
4
4
|
<% end %>
|
@@ -1,13 +1,28 @@
|
|
1
1
|
控制台 index
|
2
2
|
<%= link_to '查看文件', web_sandbox_console.view_file_path %>
|
3
|
+
<% unless session[:pass_auth] %>
|
4
|
+
<%= link_to '升级授权', web_sandbox_console.auth_page_path %>
|
5
|
+
<% end %>
|
3
6
|
|
4
|
-
<div class="console">
|
7
|
+
<div class="console margin-b-20">
|
5
8
|
<%= form_tag(web_sandbox_console.eval_code_path, remote: true, class: 'form') do %>
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
<div class= "fl text_area_div">
|
10
|
+
<%= text_area_tag :code, '', class: 'text_area_box'%>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class="submit_div">
|
14
|
+
<div class="fr">
|
15
|
+
<div class="margin-t-100"><input type="reset" value="重置" class='reset-button' %></div>
|
16
|
+
|
17
|
+
<div class="margin-t-10">
|
18
|
+
<% if session[:pass_auth] %>
|
19
|
+
<span class="tip">授权成功(当前修改将写入数据库)</span>
|
20
|
+
<% end %>
|
21
|
+
<%= submit_tag '提交', data: { disable_with: "已发送,处理中..." }, class: 'send-button' %></div>
|
22
|
+
<div class="margin-t-10"><%= submit_tag '异步执行', data: { disable_with: "已发送,处理中..." }, class: 'send-button' %></div>
|
23
|
+
</div>
|
24
|
+
</div>
|
9
25
|
<% end %>
|
10
|
-
|
11
26
|
</div>
|
12
27
|
|
13
28
|
<%= button_tag '清除输出', type: 'button', onclick: "$('.output-content').empty();", class: 'clear-button' %>
|
@@ -12,12 +12,27 @@
|
|
12
12
|
<td><%= text_field_tag :start_line_num, nil, class: 'h30', placeholder: '默认从第1行开始...' %></td>
|
13
13
|
<td>结束行数</td>
|
14
14
|
<td><%= text_field_tag :end_line_num, nil, class: 'h30', placeholder: '默认从第100行结束...' %></td>
|
15
|
+
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<td>过滤内容</td>
|
19
|
+
<td>
|
20
|
+
<%= text_field_tag :grep_content, nil, class: 'file_or_dir' %>
|
21
|
+
</td>
|
22
|
+
<td>过滤开始时间</td>
|
23
|
+
<td><%= text_field_tag :sed_start_time, nil, class: 'h30', placeholder: Time.current.strftime("%F %T") %></td>
|
24
|
+
|
25
|
+
<td>过滤结束时间</td>
|
26
|
+
<td><%= text_field_tag :sed_end_time, nil, class: 'h30', placeholder: Time.current.strftime("%F %T") %></td>
|
15
27
|
<td><%= submit_tag '提交', data: { disable_with: "已发送,处理中..." }, class: 'view-file-button' %></td>
|
28
|
+
</tr>
|
16
29
|
</table>
|
17
30
|
<% end %>
|
31
|
+
<span class="tip">注意:过滤时将忽略筛选行数</span>
|
18
32
|
</div>
|
19
33
|
|
20
34
|
<div class="output-content">
|
35
|
+
|
21
36
|
</div>
|
22
37
|
|
23
38
|
<style type="text/css">
|
data/config/routes.rb
CHANGED
@@ -4,4 +4,10 @@ WebSandboxConsole::Engine.routes.draw do
|
|
4
4
|
post :eval_code, to: "home#eval_code"
|
5
5
|
get :view_file, to: "home#view_file"
|
6
6
|
post :do_view_file, to: "home#do_view_file"
|
7
|
+
|
8
|
+
# 授权相关
|
9
|
+
get :fetch_token, to: "authorization#fetch_token"
|
10
|
+
get :auth_page, to: "authorization#auth_page"
|
11
|
+
post :auth, to: "authorization#auth"
|
7
12
|
end
|
13
|
+
|
@@ -16,10 +16,16 @@ WebSandboxConsole.setup do |config|
|
|
16
16
|
# # 配置 黑名单 实例方法
|
17
17
|
# config.instance_method_blacklist = {Kernel: %i(system exec `),File: %i(chmod chown)}
|
18
18
|
|
19
|
-
# 文件黑名单列表 (如果是目录
|
19
|
+
# 文件黑名单列表 (如果是目录 则目录下所有文件都不可用)目录以 / 结尾
|
20
20
|
# 默认都是项目路径下的
|
21
21
|
# config.view_file_blacklist = %w(config/secrets.yml vendor/)
|
22
22
|
|
23
|
+
# 配置 文件权限,是否仅能查看log文件,默开启
|
24
|
+
#config.only_view_log_file = false
|
25
|
+
|
26
|
+
# 通过非对称加密方式 升级权限,授权通过后,可获得执行数据权限(PS: 数据操作不再回滚)
|
27
|
+
# config.public_key = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMbJOE1vQT1jFpaH1GPYzdRJN/\nLh8VePmzXs5BYOLHB0xIjArL1NlXMbCJ+AS2rv3/oHIOdHhEuZw0tmm9DhG100R8\nRjBpsEKCDI88jl9qRkFmD3CVk8XQXv6c2IkRZCYSTvgDkmnKAlORksfw+p0cR2AQ\nlAtAsNsNviKYBzXKfQIDAQAB\n-----END PUBLIC KEY-----\n"
|
28
|
+
|
23
29
|
# # 配置 日志路径 默认路径位于项目下
|
24
30
|
# config.console_log_path = "log/web_sandbox_console.log"
|
25
31
|
end
|
@@ -11,14 +11,19 @@ module WebSandboxConsole
|
|
11
11
|
mattr_accessor :instance_method_blacklist
|
12
12
|
# 文件列表 黑名单
|
13
13
|
mattr_accessor :view_file_blacklist
|
14
|
+
# 仅能查看日志文件
|
15
|
+
mattr_accessor :only_view_log_file
|
14
16
|
# 日志路径
|
15
17
|
mattr_accessor :console_log_path
|
16
18
|
# 挂载 引擎路由位置
|
17
19
|
mattr_accessor :mount_engine_route_path
|
20
|
+
# 公钥
|
21
|
+
mattr_accessor :public_key
|
18
22
|
|
19
23
|
# 默认 引擎路由位置
|
20
24
|
@@mount_engine_route_path = '/web_sandbox_console'
|
21
|
-
|
25
|
+
# 默认 开启仅可查看日志
|
26
|
+
@@only_view_log_file = true
|
22
27
|
|
23
28
|
# 内置 实例方法 黑名单
|
24
29
|
INSTANT_METOD_BUILT_IN_BLACKLIST = {
|
@@ -2,22 +2,42 @@ module WebSandboxConsole
|
|
2
2
|
class Sandbox
|
3
3
|
attr_accessor :code # 代码
|
4
4
|
attr_accessor :uuid # 唯一标识
|
5
|
+
attr_accessor :pass_auth # 是否通过授权
|
5
6
|
attr_accessor :exe_tmp_file # 执行临时文件(由code 组成的运行代码)
|
6
7
|
|
7
|
-
def initialize(code = nil)
|
8
|
+
def initialize(code = nil, pass_auth = false)
|
8
9
|
@code = code
|
9
10
|
@uuid = SecureRandom.uuid
|
11
|
+
@pass_auth = pass_auth.presence || false
|
10
12
|
@exe_tmp_file = "#{Rails.root}/tmp/sandbox/#{uuid}.rb"
|
11
13
|
end
|
12
14
|
|
15
|
+
# 同步执行
|
13
16
|
def evalotor
|
17
|
+
evalotor_block do
|
18
|
+
exec_rails_runner
|
19
|
+
get_result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# 异步后台执行
|
24
|
+
def asyn_evalotor
|
25
|
+
evalotor_block do
|
26
|
+
Thread.new {exec_rails_runner}
|
27
|
+
["已在后台执行,请耐心等待😊"]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# 执行结构块
|
32
|
+
def evalotor_block
|
14
33
|
begin
|
15
34
|
check_syntax
|
16
35
|
write_exe_tmp_file
|
17
|
-
|
18
|
-
get_result
|
36
|
+
yield
|
19
37
|
rescue SandboxError => e
|
20
38
|
[e.message]
|
39
|
+
rescue Exception => e
|
40
|
+
["发生未知错误: #{e.inspect};#{e.backtrace[0..2].join('\r\n')}"]
|
21
41
|
end
|
22
42
|
end
|
23
43
|
|
@@ -28,7 +48,7 @@ module WebSandboxConsole
|
|
28
48
|
begin
|
29
49
|
ActiveRecord::Base.transaction(requires_new: true) do
|
30
50
|
result = eval(#{self.code.inspect})
|
31
|
-
raise ActiveRecord::Rollback
|
51
|
+
raise ActiveRecord::Rollback unless #{self.pass_auth}
|
32
52
|
end
|
33
53
|
rescue Exception => e
|
34
54
|
WebSandboxConsole.log_p(e, "#{self.uuid}")
|
@@ -82,11 +102,11 @@ module WebSandboxConsole
|
|
82
102
|
|
83
103
|
# 运行rails runner
|
84
104
|
def exec_rails_runner
|
85
|
-
`bundle exec rails runner #{self.exe_tmp_file}`
|
105
|
+
@stdout = `bundle exec rails runner #{self.exe_tmp_file}`
|
86
106
|
end
|
87
107
|
|
88
|
-
#
|
89
|
-
def
|
108
|
+
# 返回结果
|
109
|
+
def return_result_arr
|
90
110
|
last_10_lines = `tail -n 10 #{WebSandboxConsole.log_path} | grep #{self.uuid}`
|
91
111
|
|
92
112
|
last_10_lines.split("\n").map do |line|
|
@@ -94,5 +114,16 @@ module WebSandboxConsole
|
|
94
114
|
end.flatten
|
95
115
|
end
|
96
116
|
|
117
|
+
# 最终结果
|
118
|
+
def get_result
|
119
|
+
if @stdout.present?
|
120
|
+
stdout_arr = @stdout.to_s.split("\n")
|
121
|
+
stdout_arr << '------------ 返回值 ----------'
|
122
|
+
stdout_arr.concat(return_result_arr)
|
123
|
+
else
|
124
|
+
return_result_arr
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
97
128
|
end
|
98
129
|
end
|
@@ -3,11 +3,17 @@ module WebSandboxConsole
|
|
3
3
|
attr_accessor :file_or_dir # 文件或目录
|
4
4
|
attr_accessor :start_line_num # 起始行数
|
5
5
|
attr_accessor :end_line_num # 结束行数
|
6
|
+
attr_accessor :sed_start_time # 开始时间
|
7
|
+
attr_accessor :sed_end_time # 结束时间
|
8
|
+
attr_accessor :grep_content # 过滤内容
|
6
9
|
|
7
10
|
def initialize(opts = {})
|
8
11
|
@file_or_dir = opts[:file_or_dir]
|
9
|
-
@start_line_num = opts[:start_line_num].
|
10
|
-
@end_line_num = opts[:end_line_num].
|
12
|
+
@start_line_num = (opts[:start_line_num].presence || 1).to_i
|
13
|
+
@end_line_num = (opts[:end_line_num].presence || 100).to_i
|
14
|
+
@sed_start_time = parse_time(opts[:sed_start_time])
|
15
|
+
@sed_end_time = parse_time(opts[:sed_end_time])
|
16
|
+
@grep_content = opts[:grep_content]
|
11
17
|
end
|
12
18
|
|
13
19
|
def view
|
@@ -15,9 +21,10 @@ module WebSandboxConsole
|
|
15
21
|
check_param
|
16
22
|
file_or_dir_exists
|
17
23
|
check_blacklist
|
24
|
+
check_only_view_log
|
18
25
|
view_file
|
19
26
|
rescue ViewFileError => e
|
20
|
-
[e.message]
|
27
|
+
{lines: [e.message]}
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
@@ -74,12 +81,20 @@ module WebSandboxConsole
|
|
74
81
|
raise ViewFileError, '文件或目录无权限查看' if black_lists.include?(file_or_dir_path) || black_lists.include?(file_or_dir_path + '/')
|
75
82
|
end
|
76
83
|
|
84
|
+
# 检查 仅能查看日志
|
85
|
+
def check_only_view_log
|
86
|
+
if WebSandboxConsole.only_view_log_file
|
87
|
+
raise ViewFileError, '仅可查看日志目录/文件' unless file_or_dir.split("/")[0] == 'log'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
77
91
|
# 目录下文件
|
78
92
|
def files_in_dir
|
79
|
-
Dir["#{file_or_dir_path}/*"].map do |path|
|
93
|
+
lines = Dir["#{file_or_dir_path}/*"].map do |path|
|
80
94
|
path += is_directory?(path) ? '(目录)' : '(文件)'
|
81
95
|
path[file_or_dir_path.length..-1]
|
82
96
|
end
|
97
|
+
{lines: lines}
|
83
98
|
end
|
84
99
|
|
85
100
|
# 是否为大文件
|
@@ -87,19 +102,36 @@ module WebSandboxConsole
|
|
87
102
|
File.new(file_or_dir_path).size > 10.megabytes
|
88
103
|
end
|
89
104
|
|
105
|
+
# 是否需要过滤
|
106
|
+
def need_grep?
|
107
|
+
@sed_start_time || @grep_content.present?
|
108
|
+
end
|
109
|
+
|
90
110
|
# 查看文件/目录
|
91
111
|
def view_file
|
92
112
|
if is_directory?(file_or_dir_path)
|
93
113
|
files_in_dir
|
94
|
-
else
|
95
|
-
lines =
|
96
|
-
|
114
|
+
else # 文件
|
115
|
+
lines = if need_grep?
|
116
|
+
grep_file_content
|
117
|
+
elsif is_big_file?
|
118
|
+
tail_any_line(1000)
|
119
|
+
else
|
120
|
+
special_line_content
|
121
|
+
end
|
122
|
+
|
123
|
+
{lines: add_line_num(lines), total_line_num: cal_file_total_line_num}
|
97
124
|
end
|
98
125
|
end
|
99
126
|
|
100
|
-
#
|
101
|
-
def
|
102
|
-
|
127
|
+
# 计算文件总行数
|
128
|
+
def cal_file_total_line_num
|
129
|
+
`wc -l < #{file_or_dir_path}`.to_i
|
130
|
+
end
|
131
|
+
|
132
|
+
# 最后 xx 行内容
|
133
|
+
def tail_any_line(num)
|
134
|
+
(`tail -n #{num} #{file_or_dir_path}`).split(/[\r,\r\n]/)
|
103
135
|
end
|
104
136
|
|
105
137
|
# 按指定行返回
|
@@ -107,11 +139,31 @@ module WebSandboxConsole
|
|
107
139
|
File.readlines(file_or_dir_path)[(start_line_num - 1)..(end_line_num - 1)]
|
108
140
|
end
|
109
141
|
|
142
|
+
# 过滤文件
|
143
|
+
def grep_file_content
|
144
|
+
content = if @sed_start_time && @grep_content.present?
|
145
|
+
`sed -n '/#{@sed_start_time}/,/#{@sed_end_time}/p' #{file_or_dir_path} | grep #{@grep_content}`
|
146
|
+
elsif @sed_start_time
|
147
|
+
`sed -n '/#{@sed_start_time}/,/#{@sed_end_time}/p' #{file_or_dir_path}`
|
148
|
+
else
|
149
|
+
`grep #{@grep_content} #{file_or_dir_path}`
|
150
|
+
end
|
151
|
+
|
152
|
+
content.split(/[\r,\r\n]/)
|
153
|
+
end
|
154
|
+
|
110
155
|
# 添加行号
|
111
156
|
def add_line_num(lines)
|
112
157
|
start_num = is_big_file? ? 1 : start_line_num
|
113
158
|
lines.each_with_index.map{|line, index| "#{index + start_num}: #{line}"}
|
114
159
|
end
|
115
160
|
|
161
|
+
private
|
162
|
+
# 解析时间
|
163
|
+
def parse_time(str)
|
164
|
+
DateTime.parse(str).strftime("%FT%T") rescue nil
|
165
|
+
end
|
166
|
+
|
167
|
+
|
116
168
|
end
|
117
169
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: web_sandbox_console
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- dongmingyan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -55,13 +55,15 @@ files:
|
|
55
55
|
- app/assets/stylesheets/web_sandbox_console/application.css
|
56
56
|
- app/assets/stylesheets/web_sandbox_console/home.css
|
57
57
|
- app/controllers/web_sandbox_console/application_controller.rb
|
58
|
+
- app/controllers/web_sandbox_console/authorization_controller.rb
|
58
59
|
- app/controllers/web_sandbox_console/home_controller.rb
|
59
60
|
- app/helpers/web_sandbox_console/application_helper.rb
|
60
61
|
- app/helpers/web_sandbox_console/home_helper.rb
|
61
|
-
- app/jobs/web_sandbox_console/application_job.rb
|
62
62
|
- app/mailers/web_sandbox_console/application_mailer.rb
|
63
63
|
- app/models/web_sandbox_console/application_record.rb
|
64
64
|
- app/views/layouts/web_sandbox_console/application.html.erb
|
65
|
+
- app/views/web_sandbox_console/authorization/auth_page.html.erb
|
66
|
+
- app/views/web_sandbox_console/authorization/fetch_token.js.erb
|
65
67
|
- app/views/web_sandbox_console/home/do_view_file.js.erb
|
66
68
|
- app/views/web_sandbox_console/home/eval_code.js.erb
|
67
69
|
- app/views/web_sandbox_console/home/index.html.erb
|