import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import com.atlassian.applinks.api.ApplicationLinkResponseHandler
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.jira.JiraApplicationType
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.sal.api.net.Request
import com.atlassian.sal.api.net.Response as SalResponse
import com.atlassian.sal.api.net.ResponseException
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import groovy.transform.BaseScript
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
import java.net.URLEncoder
@BaseScript CustomEndpointDelegate delegate
/* MEDIQ V46.1 - unified Confluence DC proxy; preserves original endpoint paths and contracts.
* INSTALL: Replace the THREE existing Confluence scripts defining:
* mediqGateway*, mediqMaintenanceProxy*, mediqUserFaultProxy* with THIS ONE script.
* Never install alongside them: duplicate route registration is ambiguous.
* Existing Jira endpoints remain in Jira. Authenticated AppLink must impersonate user.
*/
def respondMediq = { int status, Object obj ->
Response.status(status).type('application/json; charset=UTF-8').entity(JsonOutput.toJson(obj)).build()
}
def invokeMediq = { String path, String method, String body, int maximum, boolean normalizeId, String operation ->
try {
// Resolve the actual Confluence request principal, not SAL remoteUserKey.
// Do not trust an identity sent by the browser or fall back to anonymous access.
def currentUser = AuthenticatedUserThreadLocal.get()
if (currentUser == null || !currentUser.name) {
return respondMediq(401,[ok:false,message:'نشست کاربری معتبر شناسایی نشد. لطفاً وارد حساب خود شوید و صفحه را دوباره باز کنید.'])
}
if (method == 'POST') {
if ((body ?: '').length() > maximum) return respondMediq(413,[ok:false,message:'حجم درخواست بیش از حد مجاز است.'])
def parsed = new JsonSlurper().parseText(body ?: '{}')
if (!(parsed instanceof Map)) return respondMediq(400,[ok:false,message:'ساختار درخواست نامعتبر است.'])
}
def appLinks = ComponentLocator.getComponent(ApplicationLinkService)
def link = appLinks?.getPrimaryApplicationLink(JiraApplicationType)
if (!link) return respondMediq(503,[ok:false,message:'اتصال سرویس مدیکیو برقرار نشده است.'])
def factory = link.createAuthenticatedRequestFactory()
def req = factory.createRequest(method == 'GET' ? Request.MethodType.GET : Request.MethodType.POST,path)
req.addHeader('Accept','application/json, text/plain')
req.setSoTimeout(90000)
if (method == 'POST') {
req.addHeader('Content-Type','application/json; charset=UTF-8')
req.addHeader('X-Atlassian-Token','no-check')
req.setRequestBody(body ?: '{}')
}
Map remote = req.execute(new ApplicationLinkResponseHandler<Map>() {
@Override Map credentialsRequired(SalResponse response) throws ResponseException {
[status:401,body:'',authRequired:true]
}
@Override Map handle(SalResponse response) throws ResponseException {
[status:response.statusCode,body:response.responseBodyAsString ?: '',authRequired:false]
}
})
int status = (remote.status ?: 502) as int
if (remote.authRequired) return respondMediq(401,[ok:false,message:'اتصال حساب کاربری به سرویس مدیکیو نیازمند تأیید دسترسی است.'])
String raw=(remote.body ?: '').toString()
if (normalizeId && status >= 200 && status < 300 && raw.trim() ==~ /^[0-9]+$/)
return respondMediq(status,[ok:true,id:raw.trim(),operation:operation])
Object parsed
try { parsed=new JsonSlurper().parseText(raw) }
catch(Exception ignored) { return respondMediq(status >= 200 && status < 300 ? 502 : status,
[ok:false,message:'پاسخ سرویس نامعتبر است.',httpStatus:status]) }
return respondMediq(status,parsed)
} catch(Exception error) {
log.error('MEDIQ UNIFIED PROXY '+method+' '+path.take(140),error)
return respondMediq(502,[ok:false,message:'ارتباط با سرویس مدیکیو برقرار نشد.'])
}
}
def apiPrefix = '/rest/scriptrunner/latest/custom/'
def kindAllowed = ['groups','titles','models','equipment','metadata','manufacturer','facility',
'supplier','department','ownerDepartment','responsible','recipient',
'accessories','ownerCompany','location'] as Set
def readGatewayMediq = { MultivaluedMap qp ->
String kind = (qp.getFirst('kind') ?: 'groups').toString()
if (!(kind in kindAllowed) && !(kind ==~ /^equipmentRef_[0-9]{1,9}$/))
return respondMediq(400,[ok:false,message:'نوع داده نامعتبر است.'])
String limit=(qp.getFirst('limit') ?: '500').toString()
String offset=(qp.getFirst('offset') ?: '0').toString()
if (!(limit ==~ /^[0-9]{1,4}$/) || !(offset ==~ /^[0-9]{1,8}$/))
return respondMediq(400,[ok:false,message:'صفحهبندی نامعتبر است.'])
int lim=Math.min(1000,Math.max(1,limit.toInteger()))
invokeMediq(apiPrefix+'mediqCatalog?kind='+URLEncoder.encode(kind,'UTF-8')+
'&limit='+lim+'&offset='+offset,'GET',null,0,false,'')
}
def writeGatewayMediq = { MultivaluedMap qp, String body ->
Map routes=[reference:'mediqCreateModelReference',model:'mediqCreateModel',
equipment:'mediqCreateEquipment',lifecycle:'mediqOperations']
String operation=(qp.getFirst('operation') ?: '').toString()
if (!routes.containsKey(operation)) return respondMediq(400,[ok:false,message:'عملیات نامعتبر است.'])
invokeMediq(apiPrefix+routes[operation],'POST',body,3000000,true,operation)
}
/* KEEP the permission boundary: catalog and write paths are for MEDIQ operators only. */
mediqGateway(httpMethod:'GET',groups:['mediq-managers','mediq-experts']) { MultivaluedMap qp,String ignored ->
readGatewayMediq(qp)
}
mediqGatewayWrite(httpMethod:'POST',groups:['mediq-managers','mediq-experts']) { MultivaluedMap qp,String body ->
writeGatewayMediq(qp,body)
}
/* Maintenance users retain server-side project permission checks. */
mediqMaintenanceProxy(httpMethod:'GET') { MultivaluedMap qp,String ignored ->
String action=(qp.getFirst('action') ?: 'overview').toString()
String type=(qp.getFirst('type') ?: '').toString()
if (!(action ==~ /^[a-z][a-z0-9.]{0,39}$/) || type.length()>100)
return respondMediq(400,[ok:false,message:'پارامتر نامعتبر است.'])
String path=apiPrefix+'mediqMaintenance?action='+URLEncoder.encode(action,'UTF-8')
if(type) path+='&type='+URLEncoder.encode(type,'UTF-8')
invokeMediq(path,'GET',null,0,false,'')
}
mediqMaintenanceProxyWrite(httpMethod:'POST') { MultivaluedMap qp,String body ->
invokeMediq(apiPrefix+'mediqMaintenanceWrite','POST',body,20000,false,'')
}
/* Self-service: on Jira backend, access is restricted to the authenticated reporter. */
mediqUserFaultProxy(httpMethod:'GET') { MultivaluedMap qp,String ignored ->
String action=(qp.getFirst('action') ?: 'list').toString()
String key=(qp.getFirst('key') ?: '').toString()
if (!(action in ['list','detail']) || (key && !(key ==~ /^MED-[0-9]{1,12}$/)))
return respondMediq(400,[ok:false,message:'پارامتر نامعتبر است.'])
String path=apiPrefix+'mediqUserFault?action='+URLEncoder.encode(action,'UTF-8')
if(key)path+='&key='+URLEncoder.encode(key,'UTF-8')
invokeMediq(path,'GET',null,0,false,'')
}
mediqUserFaultProxyWrite(httpMethod:'POST') { MultivaluedMap qp,String body ->
invokeMediq(apiPrefix+'mediqUserFaultWrite','POST',body,12000,false,'')
} |